Files
gimp/plug-ins/common/tiff.c
Tor Lillqvist 970ad834d1 libgimp/gimp.def libgimp/gimpui.def libgimp/makefile.{cygwin,msc}
2000-02-15  Tor Lillqvist  <tml@iki.fi>

* libgimp/gimp.def
* libgimp/gimpui.def
* libgimp/makefile.{cygwin,msc}
* app/makefile.{cygwin,msc}
* plug-ins/makefile.{cygwin,msc}: Updates.

* app/datafiles.c (is_script): New Win32-only function, which
tests if a file's extension matches one of the extensions in the
PATHEXT environment variable (which the cmd.exe command
interpreter also uses). This is to avoid starting applications
associated with any random data file the user might have dropped
in the plug-ins folder, while still supporting plug-ins written in
scripting languages.

* app/gimpparasite.c (gimp_parasiterc_save): (Win32:) Cannot
rename to an existing file.

* plug-ins/Lighting/lighting_image.c
* plug-ins/Lighting/lighting_share.c
* plug-ins/MapObject/mapobject_preview.c
* plug-ins/MapObject/mapobject_shade.c: Use G_PI.

* plug-ins/common/gz.c: #ifdef G_OS_WIN32 was used before its
potential definition via glib.h.

* plug-ins/common/jpeg.c: Also recognize Exif files, which are
typically produced by digital cameras. The usually have a .jpg
file name extension, and would thus already match this plug-in,
but add the magic string just in case. They are loaded just fine
by libjpeg even if they don't have the JFIF signature.

* plug-ins/common/tiff.c: Set TIFF warning and error handler, so
we get to pass libtiff's messages through the normal channels.
2000-02-14 22:44:06 +00:00

1555 lines
43 KiB
C

/* tiff loading and saving for the GIMP
* -Peter Mattis
* The TIFF loading code has been completely revamped by Nick Lamb
* njl195@zepler.org.uk -- 18 May 1998
* And it now gains support for tiles (and doubtless a zillion bugs)
* njl195@zepler.org.uk -- 12 June 1999
* The code for this filter is based on "tifftopnm" and "pnmtotiff",
* 2 programs that are a part of the netpbm package.
*/
/*
** tifftopnm.c - converts a Tagged Image File to a portable anymap
**
** Derived by Jef Poskanzer from tif2ras.c, which is:
**
** Copyright (c) 1990 by Sun Microsystems, Inc.
**
** Author: Patrick J. Naughton
** naughton@wind.sun.com
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted,
** provided that the above copyright notice appear in all copies and that
** both that copyright notice and this permission notice appear in
** supporting documentation.
**
** This file is provided AS IS with no warranties of any kind. The author
** shall have no liability with respect to the infringement of copyrights,
** trade secrets or any patents by this file or any part thereof. In no
** event will the author be liable for any lost revenue or profits or
** other special, indirect and consequential damages.
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <tiffio.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"
typedef struct
{
gint compression;
gint fillorder;
} TiffSaveVals;
typedef struct
{
gint run;
} TiffSaveInterface;
typedef struct
{
gint32 ID;
GDrawable *drawable;
GPixelRgn pixel_rgn;
guchar *pixels;
guchar *pixel;
} channel_data;
/* Declare some local functions.
*/
static void query (void);
static void run (gchar *name,
gint nparams,
GParam *param,
gint *nreturn_vals,
GParam **return_vals);
static gint32 load_image (gchar *filename);
static void load_rgba (TIFF *tif,
channel_data *channel);
static void load_lines (TIFF *tif,
channel_data *channel,
gushort bps,
gushort photomet,
gint alpha,
gint extra);
static void load_tiles (TIFF *tif,
channel_data *channel,
gushort bps,
gushort photomet,
gint alpha,
gint extra);
static void read_separate (guchar *source,
channel_data *channel,
gushort bps,
gushort photomet,
gint startcol,
gint startrow,
gint rows,
gint cols,
gint alpha,
gint extra,
gint sample);
static void read_16bit (guchar *source,
channel_data *channel,
gushort photomet,
gint startcol,
gint startrow,
gint rows,
gint cols,
gint alpha,
gint extra);
static void read_8bit (guchar *source,
channel_data *channel,
gushort photomet,
gint startcol,
gint startrow,
gint rows,
gint cols,
gint alpha,
gint extra);
static void read_default (guchar *source,
channel_data *channel,
gushort bps,
gushort photomet,
gint startcol,
gint startrow,
gint rows,
gint cols,
gint alpha,
gint extra);
static gint save_image (gchar *filename,
gint32 image,
gint32 drawable,
gint32 orig_image);
static void init_gtk (void);
static gint save_dialog (void);
static void save_ok_callback (GtkWidget *widget,
gpointer data);
static void comment_entry_callback (GtkWidget *widget,
gpointer data);
#define DEFAULT_COMMENT "Created with The GIMP"
GPlugInInfo PLUG_IN_INFO =
{
NULL, /* init_proc */
NULL, /* quit_proc */
query, /* query_proc */
run, /* run_proc */
};
static TiffSaveVals tsvals =
{
COMPRESSION_LZW, /* compression */
};
static TiffSaveInterface tsint =
{
FALSE /* run */
};
static char *image_comment= NULL;
MAIN ()
static void
query (void)
{
static GParamDef load_args[] =
{
{ PARAM_INT32, "run_mode", "Interactive, non-interactive" },
{ PARAM_STRING, "filename", "The name of the file to load" },
{ PARAM_STRING, "raw_filename", "The name of the file to load" },
};
static GParamDef load_return_vals[] =
{
{ PARAM_IMAGE, "image", "Output image" },
};
static gint nload_args = sizeof (load_args) / sizeof (load_args[0]);
static gint nload_return_vals = (sizeof (load_return_vals) /
sizeof (load_return_vals[0]));
static GParamDef save_args[] =
{
{ PARAM_INT32, "run_mode", "Interactive, non-interactive" },
{ PARAM_IMAGE, "image", "Input image" },
{ PARAM_DRAWABLE, "drawable", "Drawable to save" },
{ PARAM_STRING, "filename", "The name of the file to save the image in" },
{ PARAM_STRING, "raw_filename", "The name of the file to save the image in" },
{ PARAM_INT32, "compression", "Compression type: { NONE (0), LZW (1), PACKBITS (2)" },
};
static gint nsave_args = sizeof (save_args) / sizeof (save_args[0]);
INIT_I18N();
gimp_install_procedure ("file_tiff_load",
"loads files of the tiff file format",
"FIXME: write help for tiff_load",
"Spencer Kimball, Peter Mattis & Nick Lamb",
"Nick Lamb <njl195@zepler.org.uk>",
"1995-1996,1998-1999",
"<Load>/Tiff",
NULL,
PROC_PLUG_IN,
nload_args, nload_return_vals,
load_args, load_return_vals);
gimp_install_procedure ("file_tiff_save",
"saves files in the tiff file format",
"FIXME: write help for tiff_save",
"Spencer Kimball & Peter Mattis",
"Spencer Kimball & Peter Mattis",
"1995-1996",
"<Save>/Tiff",
"RGB*, GRAY*, INDEXED",
PROC_PLUG_IN,
nsave_args, 0,
save_args, NULL);
gimp_register_magic_load_handler ("file_tiff_load",
"tif,tiff",
"",
"0,string,II*\\0,0,string,MM\\0*");
gimp_register_save_handler ("file_tiff_save",
"tif,tiff",
"");
}
static void
run (gchar *name,
gint nparams,
GParam *param,
gint *nreturn_vals,
GParam **return_vals)
{
static GParam values[2];
GRunModeType run_mode;
GStatusType status = STATUS_SUCCESS;
#ifdef GIMP_HAVE_PARASITES
Parasite *parasite;
#endif /* GIMP_HAVE_PARASITES */
gint32 image;
gint32 drawable;
gint32 orig_image;
GimpExportReturnType export = EXPORT_CANCEL;
run_mode = param[0].data.d_int32;
*nreturn_vals = 1;
*return_vals = values;
values[0].type = PARAM_STATUS;
values[0].data.d_status = STATUS_EXECUTION_ERROR;
if (strcmp (name, "file_tiff_load") == 0)
{
INIT_I18N();
image = load_image (param[1].data.d_string);
if (image != -1)
{
*nreturn_vals = 2;
values[1].type = PARAM_IMAGE;
values[1].data.d_image = image;
}
else
{
status = STATUS_EXECUTION_ERROR;
}
}
else if (strcmp (name, "file_tiff_save") == 0)
{
image = orig_image = param[1].data.d_int32;
drawable = param[2].data.d_int32;
/* Do this right this time, if POSSIBLE query for parasites, otherwise
or if there isn't one, choose the DEFAULT_COMMENT */
/* eventually export the image */
switch (run_mode)
{
case RUN_INTERACTIVE:
case RUN_WITH_LAST_VALS:
INIT_I18N_UI();
init_gtk ();
export = gimp_export_image (&image, &drawable, "TIFF",
(CAN_HANDLE_RGB |
CAN_HANDLE_GRAY |
CAN_HANDLE_INDEXED |
CAN_HANDLE_ALPHA));
if (export == EXPORT_CANCEL)
{
values[0].data.d_status = STATUS_CANCEL;
return;
}
break;
default:
break;
}
#ifdef GIMP_HAVE_PARASITES
parasite = gimp_image_parasite_find (orig_image, "gimp-comment");
if (parasite)
image_comment = g_strdup (parasite->data);
parasite_free (parasite);
#endif /* GIMP_HAVE_PARASITES */
if (!image_comment)
image_comment = g_strdup (DEFAULT_COMMENT);
switch (run_mode)
{
case RUN_INTERACTIVE:
/* Possibly retrieve data */
gimp_get_data ("file_tiff_save", &tsvals);
#ifdef GIMP_HAVE_PARASITES
parasite = gimp_image_parasite_find (orig_image, "tiff-save-options");
if (parasite)
{
tsvals.compression =
((TiffSaveVals *) parasite->data)->compression;
}
parasite_free (parasite);
#endif /* GIMP_HAVE_PARASITES */
/* First acquire information with a dialog */
if (! save_dialog ())
status = STATUS_CANCEL;
break;
case RUN_NONINTERACTIVE:
INIT_I18N();
/* Make sure all the arguments are there! */
if (nparams != 6)
{
status = STATUS_CALLING_ERROR;
}
else
{
switch (param[5].data.d_int32)
{
case 0: tsvals.compression = COMPRESSION_NONE; break;
case 1: tsvals.compression = COMPRESSION_LZW; break;
case 2: tsvals.compression = COMPRESSION_PACKBITS; break;
default: status = STATUS_CALLING_ERROR; break;
}
}
case RUN_WITH_LAST_VALS:
/* Possibly retrieve data */
gimp_get_data ("file_tiff_save", &tsvals);
#ifdef GIMP_HAVE_PARASITES
parasite = gimp_image_parasite_find (orig_image, "tiff-save-options");
if (parasite)
{
tsvals.compression =
((TiffSaveVals *) parasite->data)->compression;
}
parasite_free (parasite);
#endif /* GIMP_HAVE_PARASITES */
break;
default:
break;
}
if (status == STATUS_SUCCESS)
{
if (save_image (param[3].data.d_string, image, drawable, orig_image))
{
/* Store mvals data */
gimp_set_data ("file_tiff_save", &tsvals, sizeof (TiffSaveVals));
}
else
{
status = STATUS_EXECUTION_ERROR;
}
}
if (export == EXPORT_EXPORT)
gimp_image_delete (image);
}
else
{
status = STATUS_CALLING_ERROR;
}
values[0].data.d_status = status;
}
static void
tiff_warning(const char* module, const char* fmt, va_list ap)
{
g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, ap);
}
static void
tiff_error(const char* module, const char* fmt, va_list ap)
{
g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, ap);
gimp_quit ();
}
static gint32
load_image (gchar *filename)
{
TIFF *tif;
unsigned short bps, spp, photomet;
int cols, rows, alpha;
int image, image_type = RGB;
int layer, layer_type = RGB_IMAGE;
unsigned short extra, *extra_types;
channel_data *channel= NULL;
unsigned short *redmap, *greenmap, *bluemap;
guchar colors[3]= {0, 0, 0};
guchar cmap[768];
int i, j, worst_case = 0;
char *name;
TiffSaveVals save_vals;
#ifdef GIMP_HAVE_PARASITES
Parasite *parasite;
#endif /* GIMP_HAVE_PARASITES */
guint16 tmp;
TIFFSetWarningHandler (tiff_warning);
TIFFSetErrorHandler (tiff_error);
tif = TIFFOpen (filename, "r");
if (!tif) {
g_message ("TIFF Can't open %s\n", filename);
gimp_quit ();
}
name = g_strdup_printf( _("Loading %s:"), filename);
gimp_progress_init (name);
g_free (name);
TIFFGetFieldDefaulted (tif, TIFFTAG_BITSPERSAMPLE, &bps);
if (bps > 8 && bps != 16) {
worst_case = 1; /* Wrong sample width => RGBA */
}
TIFFGetFieldDefaulted (tif, TIFFTAG_SAMPLESPERPIXEL, &spp);
if (!TIFFGetField (tif, TIFFTAG_EXTRASAMPLES, &extra, &extra_types))
extra = 0;
if (!TIFFGetField (tif, TIFFTAG_IMAGEWIDTH, &cols)) {
g_message ("TIFF Can't get image width\n");
gimp_quit ();
}
if (!TIFFGetField (tif, TIFFTAG_IMAGELENGTH, &rows)) {
g_message ("TIFF Can't get image length\n");
gimp_quit ();
}
if (!TIFFGetField (tif, TIFFTAG_PHOTOMETRIC, &photomet)) {
g_message("TIFF Can't get photometric\nAssuming min-is-black\n");
/* old AppleScan software misses out the photometric tag (and
* incidentally assumes min-is-white, but xv assumes min-is-black,
* so we follow xv's lead. It's not much hardship to invert the
* image later). */
photomet = PHOTOMETRIC_MINISBLACK;
}
/* test if the extrasample represents an associated alpha channel... */
if (extra > 0 && (extra_types[0] == EXTRASAMPLE_ASSOCALPHA)) {
alpha = 1;
--extra;
} else {
alpha = 0;
}
if (photomet == PHOTOMETRIC_RGB && spp > 3 + extra) {
alpha= 1;
extra= spp - 4;
} else if (photomet != PHOTOMETRIC_RGB && spp > 1 + extra) {
alpha= 1;
extra= spp - 2;
}
switch (photomet) {
case PHOTOMETRIC_MINISBLACK:
case PHOTOMETRIC_MINISWHITE:
image_type = GRAY;
layer_type = (alpha) ? GRAYA_IMAGE : GRAY_IMAGE;
break;
case PHOTOMETRIC_RGB:
image_type = RGB;
layer_type = (alpha) ? RGBA_IMAGE : RGB_IMAGE;
break;
case PHOTOMETRIC_PALETTE:
image_type = INDEXED;
layer_type = (alpha) ? INDEXEDA_IMAGE : INDEXED_IMAGE;
break;
default:
worst_case = 1;
}
if (worst_case) {
image_type = RGB;
layer_type = RGBA_IMAGE;
}
if ((image = gimp_image_new (cols, rows, image_type)) == -1) {
g_message("TIFF Can't create a new image\n");
gimp_quit ();
}
gimp_image_set_filename (image, filename);
/* attach a parasite containing the compression/fillorder */
if (!TIFFGetField (tif, TIFFTAG_COMPRESSION, &tmp))
save_vals.compression = COMPRESSION_NONE;
else
save_vals.compression = tmp;
#ifdef GIMP_HAVE_PARASITES
parasite = parasite_new("tiff-save-options", 0,
sizeof(save_vals), &save_vals);
gimp_image_parasite_attach(image, parasite);
parasite_free(parasite);
#endif /* GIMP_HAVE_PARASITES */
/* Attach a parasite containing the image description. Pretend to
* be a gimp comment so other plugins will use this description as
* an image comment where appropriate. */
#ifdef GIMP_HAVE_PARASITES
{
char *img_desc;
if (TIFFGetField (tif, TIFFTAG_IMAGEDESCRIPTION, &img_desc))
{
int len;
len = strlen(img_desc) + 1;
len = MIN(len, 241);
img_desc[len-1] = '\000';
parasite = parasite_new ("gimp-comment", PARASITE_PERSISTENT,
len, img_desc);
gimp_image_parasite_attach (image, parasite);
parasite_free (parasite);
}
}
#endif /* GIMP_HAVE_PARASITES */
/* any resolution info in the file? */
#ifdef GIMP_HAVE_RESOLUTION_INFO
{
gfloat xres = 72.0, yres = 72.0;
gushort read_unit;
GimpUnit unit = GIMP_UNIT_PIXEL; /* invalid unit */
if (TIFFGetField (tif, TIFFTAG_XRESOLUTION, &xres)) {
if (TIFFGetField (tif, TIFFTAG_YRESOLUTION, &yres)) {
if (TIFFGetFieldDefaulted (tif, TIFFTAG_RESOLUTIONUNIT, &read_unit))
{
switch (read_unit)
{
case RESUNIT_NONE:
/* ImageMagick writes files with this silly resunit */
g_message ("TIFF warning: resolution units meaningless\n");
break;
case RESUNIT_INCH:
unit = GIMP_UNIT_INCH;
break;
case RESUNIT_CENTIMETER:
xres *= 2.54;
yres *= 2.54;
unit = GIMP_UNIT_MM; /* as this is our default metric unit */
break;
default:
g_message ("TIFF file error: unknown resolution unit type %d, "
"assuming dpi\n", read_unit);
break;
}
}
else
{ /* no res unit tag */
/* old AppleScan software produces these */
g_message ("TIFF warning: resolution specified without\n"
"any units tag, assuming dpi\n");
}
}
else
{ /* xres but no yres */
g_message("TIFF warning: no y resolution info, assuming same as x\n");
yres = xres;
}
/* now set the new image's resolution info */
/* If it is invalid, instead of forcing 72dpi, do not set the resolution
at all. Gimp will then use the default set by the user */
if (read_unit != RESUNIT_NONE)
{
gimp_image_set_resolution (image, xres, yres);
if (unit != GIMP_UNIT_PIXEL)
gimp_image_set_unit (image, unit);
}
}
/* no x res tag => we assume we have no resolution info, so we
* don't care. Older versions of this plugin used to write files
* with no resolution tags at all. */
/* TODO: haven't caught the case where yres tag is present, but
not xres. This is left as an exercise for the reader - they
should feel free to shoot the author of the broken program
that produced the damaged TIFF file in the first place. */
}
#endif /* GIMP_HAVE_RESOLUTION_INFO */
/* Install colormap for INDEXED images only */
if (image_type == INDEXED)
{
if (!TIFFGetField (tif, TIFFTAG_COLORMAP, &redmap, &greenmap, &bluemap))
{
g_message ("TIFF Can't get colormaps\n");
gimp_quit ();
}
for (i = 0, j = 0; i < (1 << bps); i++)
{
cmap[j++] = redmap[i] >> 8;
cmap[j++] = greenmap[i] >> 8;
cmap[j++] = bluemap[i] >> 8;
}
gimp_image_set_cmap (image, cmap, (1 << bps));
}
/* Allocate channel_data for all channels, even the background layer */
channel = g_new (channel_data, extra + 1);
layer = gimp_layer_new (image, _("Background"), cols, rows, layer_type,
100, NORMAL_MODE);
channel[0].ID= layer;
gimp_image_add_layer (image, layer, 0);
channel[0].drawable= gimp_drawable_get(layer);
if (extra > 0 && !worst_case) {
/* Add alpha channels as appropriate */
for (i= 1; i <= extra; ++i) {
channel[i].ID= gimp_channel_new(image, _("TIFF Channel"), cols, rows,
100.0, colors);
gimp_image_add_channel(image, channel[i].ID, 0);
channel[i].drawable= gimp_drawable_get (channel[i].ID);
}
}
if (worst_case) {
g_message("TIFF Fell back to RGBA, image may be inverted\n");
load_rgba (tif, channel);
} else if (TIFFIsTiled(tif)) {
load_tiles (tif, channel, bps, photomet, alpha, extra);
} else { /* Load scanlines in tile_height chunks */
load_lines (tif, channel, bps, photomet, alpha, extra);
}
for (i= 0; !worst_case && i < extra; ++i) {
gimp_drawable_flush (channel[i].drawable);
gimp_drawable_detach (channel[i].drawable);
}
return image;
}
static void
load_rgba (TIFF *tif, channel_data *channel)
{
uint32 imageWidth, imageLength;
gulong *buffer;
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &imageWidth);
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imageLength);
gimp_pixel_rgn_init (&(channel[0].pixel_rgn), channel[0].drawable,
0, 0, imageWidth, imageLength, TRUE, FALSE);
buffer = g_new(gulong, imageWidth * imageLength);
channel[0].pixels = (guchar*) buffer;
if (buffer == NULL) {
g_message("TIFF Unable to allocate temporary buffer\n");
}
if (!TIFFReadRGBAImage(tif, imageWidth, imageLength, buffer, 0))
g_message("TIFF Unsupported layout, no RGBA loader\n");
gimp_pixel_rgn_set_rect(&(channel[0].pixel_rgn), channel[0].pixels,
0, 0, imageWidth, imageLength);
}
static void
load_tiles (TIFF *tif, channel_data *channel,
unsigned short bps, unsigned short photomet,
int alpha, int extra)
{
uint16 planar= PLANARCONFIG_CONTIG;
uint32 imageWidth, imageLength;
uint32 tileWidth, tileLength;
uint32 x, y, rows, cols;
guchar *buffer;
double progress= 0.0, one_row;
int i;
TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planar);
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &imageWidth);
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imageLength);
TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tileWidth);
TIFFGetField(tif, TIFFTAG_TILELENGTH, &tileLength);
one_row = (double) tileLength / (double) imageLength;
buffer = g_malloc(TIFFTileSize(tif));
for (i= 0; i <= extra; ++i) {
channel[i].pixels= g_new(guchar, tileWidth * tileLength *
channel[i].drawable->bpp);
}
for (y = 0; y < imageLength; y += tileLength) {
for (x = 0; x < imageWidth; x += tileWidth) {
gimp_progress_update (progress + one_row *
( (double) x / (double) imageWidth));
TIFFReadTile(tif, buffer, x, y, 0, 0);
cols= MIN(imageWidth - x, tileWidth);
rows= MIN(imageLength - y, tileLength);
if (bps == 16) {
read_16bit(buffer, channel, photomet, y, x, rows, cols, alpha, extra);
} else if (bps == 8) {
read_8bit(buffer, channel, photomet, y, x, rows, cols, alpha, extra);
} else {
read_default(buffer, channel, bps, photomet,
y, x, rows, cols, alpha, extra);
}
}
progress+= one_row;
}
for (i= 0; i <= extra; ++i) {
g_free(channel[i].pixels);
}
g_free(buffer);
}
static void
load_lines (TIFF *tif, channel_data *channel,
unsigned short bps, unsigned short photomet,
int alpha, int extra)
{
uint16 planar= PLANARCONFIG_CONTIG;
uint32 imageLength, lineSize, cols, rows;
guchar *buffer;
int i, y, tile_height = gimp_tile_height ();
TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planar);
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imageLength);
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &cols);
lineSize= TIFFScanlineSize(tif);
for (i= 0; i <= extra; ++i) {
channel[i].pixels= g_new(guchar, tile_height * cols
* channel[i].drawable->bpp);
}
buffer = g_malloc(lineSize * tile_height);
if (planar == PLANARCONFIG_CONTIG) {
for (y = 0; y < imageLength; y+= tile_height ) {
gimp_progress_update ( (double) y / (double) imageLength);
rows = MIN(tile_height, imageLength - y);
for (i = 0; i < rows; ++i)
TIFFReadScanline(tif, buffer + i * lineSize, y + i, 0);
if (bps == 16) {
read_16bit(buffer, channel, photomet, y, 0, rows, cols, alpha, extra);
} else if (bps == 8) {
read_8bit(buffer, channel, photomet, y, 0, rows, cols, alpha, extra);
} else {
read_default(buffer, channel, bps, photomet,
y, 0, rows, cols, alpha, extra);
}
}
} else { /* PLANARCONFIG_SEPARATE -- Just say "No" */
uint16 s, samples;
TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samples);
for (s = 0; s < samples; ++s) {
for (y = 0; y < imageLength; y+= tile_height ) {
gimp_progress_update ( (double) y / (double) imageLength);
rows = MIN(tile_height, imageLength - y);
for (i = 0; i < rows; ++i)
TIFFReadScanline(tif, buffer + i * lineSize, y + i, s);
read_separate (buffer, channel, bps, photomet,
y, 0, rows, cols, alpha, extra, s);
}
}
}
for (i= 0; i <= extra; ++i) {
g_free(channel[i].pixels);
}
g_free(buffer);
}
static void
read_16bit (guchar *source, channel_data *channel, unsigned short photomet,
int startrow, int startcol, int rows, int cols,
int alpha, int extra)
{
guchar *dest;
int gray_val, red_val, green_val, blue_val, alpha_val;
int col, row, i;
for (i= 0; i <= extra; ++i) {
gimp_pixel_rgn_init (&(channel[i].pixel_rgn), channel[i].drawable,
startcol, startrow, cols, rows, TRUE, FALSE);
}
for (row = 0; row < rows; ++row) {
dest= channel[0].pixels + row * cols * channel[0].drawable->bpp;
for (i= 1; i <= extra; ++i) {
channel[i].pixel= channel[i].pixels + row * cols;
}
for (col = 0; col < cols; col++) {
switch (photomet) {
case PHOTOMETRIC_MINISBLACK:
if (alpha) {
gray_val= *source; source+= 2;
alpha_val= *source; source+= 2;
if (alpha_val)
*dest++ = gray_val * 255 / alpha_val;
else
*dest++ = 0;
*dest++ = alpha_val;
} else {
*dest++ = *source; source+= 2;
}
break;
case PHOTOMETRIC_MINISWHITE:
if (alpha) {
gray_val= *source; source+= 2;
alpha_val= *source; source+= 2;
if (alpha_val)
*dest++ = ((255 - gray_val) * 255) / alpha_val;
else
*dest++ = 0;
*dest++ = alpha_val;
} else {
*dest++ = ~(*source); source+= 2;
}
break;
case PHOTOMETRIC_PALETTE:
*dest++= *source; source+= 2;
if (alpha) *dest++= *source; source+= 2;
break;
case PHOTOMETRIC_RGB:
if (alpha) {
red_val= *source; source+= 2;
green_val= *source; source+= 2;
blue_val= *source; source+= 2;
alpha_val= *source; source+= 2;
if (alpha_val) {
*dest++ = (red_val * 255) / alpha_val;
*dest++ = (green_val * 255) / alpha_val;
*dest++ = (blue_val * 255) / alpha_val;
} else {
*dest++ = 0;
*dest++ = 0;
*dest++ = 0;
}
*dest++ = alpha_val;
} else {
*dest++ = *source; source+= 2;
*dest++ = *source; source+= 2;
*dest++ = *source; source+= 2;
}
break;
default:
/* This case was handled earlier */
g_assert_not_reached();
}
for (i= 1; i <= extra; ++i) {
*channel[i].pixel++ = *source; source+= 2;
}
}
}
for (i= 0; i <= extra; ++i) {
gimp_pixel_rgn_set_rect(&(channel[i].pixel_rgn), channel[i].pixels,
startcol, startrow, cols, rows);
}
}
static void
read_8bit (guchar *source, channel_data *channel, unsigned short photomet,
int startrow, int startcol, int rows, int cols,
int alpha, int extra)
{
guchar *dest;
int gray_val, red_val, green_val, blue_val, alpha_val;
int col, row, i;
for (i= 0; i <= extra; ++i) {
gimp_pixel_rgn_init (&(channel[i].pixel_rgn), channel[i].drawable,
startcol, startrow, cols, rows, TRUE, FALSE);
}
for (row = 0; row < rows; ++row) {
dest= channel[0].pixels + row * cols * channel[0].drawable->bpp;
for (i= 1; i <= extra; ++i) {
channel[i].pixel= channel[i].pixels + row * cols;
}
for (col = 0; col < cols; col++) {
switch (photomet) {
case PHOTOMETRIC_MINISBLACK:
if (alpha) {
gray_val= *source++;
alpha_val= *source++;
if (alpha_val)
*dest++ = gray_val * 255 / alpha_val;
else
*dest++ = 0;
*dest++ = alpha_val;
} else {
*dest++ = *source++;
}
break;
case PHOTOMETRIC_MINISWHITE:
if (alpha) {
gray_val= *source++;
alpha_val= *source++;
if (alpha_val)
*dest++ = ((255 - gray_val) * 255) / alpha_val;
else
*dest++ = 0;
*dest++ = alpha_val;
} else {
*dest++ = ~(*source++);
}
break;
case PHOTOMETRIC_PALETTE:
*dest++= *source++;
if (alpha) *dest++= *source++;
break;
case PHOTOMETRIC_RGB:
if (alpha) {
red_val= *source++;
green_val= *source++;
blue_val= *source++;
alpha_val= *source++;
if (alpha_val) {
*dest++ = (red_val * 255) / alpha_val;
*dest++ = (green_val * 255) / alpha_val;
*dest++ = (blue_val * 255) / alpha_val;
} else {
*dest++ = 0;
*dest++ = 0;
*dest++ = 0;
}
*dest++ = alpha_val;
} else {
*dest++ = *source++;
*dest++ = *source++;
*dest++ = *source++;
}
break;
default:
/* This case was handled earlier */
g_assert_not_reached();
}
for (i= 1; i <= extra; ++i) {
*channel[i].pixel++ = *source++;
}
}
}
for (i= 0; i <= extra; ++i) {
gimp_pixel_rgn_set_rect(&(channel[i].pixel_rgn), channel[i].pixels,
startcol, startrow, cols, rows);
}
}
/* Step through all <= 8-bit samples in an image */
#define NEXTSAMPLE(var) \
{ \
if (bitsleft == 0) \
{ \
source++; \
bitsleft = 8; \
} \
bitsleft -= bps; \
var = ( *source >> bitsleft ) & maxval; \
}
static void
read_default (guchar *source, channel_data *channel,
unsigned short bps, unsigned short photomet,
int startrow, int startcol, int rows, int cols,
int alpha, int extra)
{
guchar *dest;
int gray_val, red_val, green_val, blue_val, alpha_val;
int col, row, i;
int bitsleft = 8, maxval = (1 << bps) - 1;
for (i= 0; i <= extra; ++i) {
gimp_pixel_rgn_init (&(channel[i].pixel_rgn), channel[i].drawable,
startcol, startrow, cols, rows, TRUE, FALSE);
}
for (row = 0; row < rows; ++row) {
dest= channel[0].pixels + row * cols * channel[0].drawable->bpp;
for (i= 1; i <= extra; ++i) {
channel[i].pixel= channel[i].pixels + row * cols;
}
for (col = 0; col < cols; col++) {
switch (photomet) {
case PHOTOMETRIC_MINISBLACK:
NEXTSAMPLE(gray_val);
if (alpha) {
NEXTSAMPLE(alpha_val);
if (alpha_val)
*dest++ = (gray_val * 65025) / (alpha_val * maxval);
else
*dest++ = 0;
*dest++ = alpha_val;
} else {
*dest++ = (gray_val * 255) / maxval;
}
break;
case PHOTOMETRIC_MINISWHITE:
NEXTSAMPLE(gray_val);
if (alpha) {
NEXTSAMPLE(alpha_val);
if (alpha_val)
*dest++ = ((maxval - gray_val) * 65025) / (alpha_val * maxval);
else
*dest++ = 0;
*dest++ = alpha_val;
} else {
*dest++ = ((maxval - gray_val) * 255) / maxval;
}
break;
case PHOTOMETRIC_PALETTE:
NEXTSAMPLE(*dest++);
if (alpha) {
NEXTSAMPLE(*dest++);
}
break;
case PHOTOMETRIC_RGB:
NEXTSAMPLE(red_val)
NEXTSAMPLE(green_val)
NEXTSAMPLE(blue_val)
if (alpha) {
NEXTSAMPLE(alpha_val)
if (alpha_val) {
*dest++ = (red_val * 255) / alpha_val;
*dest++ = (green_val * 255) / alpha_val;
*dest++ = (blue_val * 255) / alpha_val;
} else {
*dest++ = 0;
*dest++ = 0;
*dest++ = 0;
}
*dest++ = alpha_val;
} else {
*dest++ = red_val;
*dest++ = green_val;
*dest++ = blue_val;
}
break;
default:
/* This case was handled earlier */
g_assert_not_reached();
}
for (i= 1; i <= extra; ++i) {
NEXTSAMPLE(alpha_val);
*channel[i].pixel++ = alpha_val;
}
}
bitsleft= 0;
}
for (i= 0; i <= extra; ++i) {
gimp_pixel_rgn_set_rect(&(channel[i].pixel_rgn), channel[i].pixels,
startcol, startrow, cols, rows);
}
}
static void
read_separate (guchar *source, channel_data *channel,
unsigned short bps, unsigned short photomet,
int startrow, int startcol, int rows, int cols,
int alpha, int extra, int sample)
{
guchar *dest;
int col, row, c;
int bitsleft = 8, maxval = (1 << bps) - 1;
if (bps > 8) {
g_message("TIFF Unsupported layout\n");
gimp_quit();
}
if (sample < channel[0].drawable->bpp) {
c = 0;
} else {
c = (sample - channel[0].drawable->bpp) + 4;
photomet = PHOTOMETRIC_MINISBLACK;
}
gimp_pixel_rgn_init (&(channel[c].pixel_rgn), channel[c].drawable,
startcol, startrow, cols, rows, TRUE, FALSE);
gimp_pixel_rgn_get_rect(&(channel[c].pixel_rgn), channel[c].pixels,
startcol, startrow, cols, rows);
for (row = 0; row < rows; ++row) {
dest = channel[c].pixels + row * cols * channel[c].drawable->bpp;
if (c == 0) {
for (col = 0; col < cols; ++col) {
NEXTSAMPLE(dest[col * channel[0].drawable->bpp + sample]);
}
} else {
for (col = 0; col < cols; ++col)
NEXTSAMPLE(dest[col]);
}
}
gimp_pixel_rgn_set_rect(&(channel[c].pixel_rgn), channel[c].pixels,
startcol, startrow, cols, rows);
}
/*
** pnmtotiff.c - converts a portable anymap to a Tagged Image File
**
** Derived by Jef Poskanzer from ras2tif.c, which is:
**
** Copyright (c) 1990 by Sun Microsystems, Inc.
**
** Author: Patrick J. Naughton
** naughton@wind.sun.com
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted,
** provided that the above copyright notice appear in all copies and that
** both that copyright notice and this permission notice appear in
** supporting documentation.
**
** This file is provided AS IS with no warranties of any kind. The author
** shall have no liability with respect to the infringement of copyrights,
** trade secrets or any patents by this file or any part thereof. In no
** event will the author be liable for any lost revenue or profits or
** other special, indirect and consequential damages.
*/
static gint save_image (char *filename,
gint32 image,
gint32 layer,
gint32 orig_image) /* the export function might have created a duplicate */
{
TIFF *tif;
unsigned short red[256];
unsigned short grn[256];
unsigned short blu[256];
int cols, col, rows, row, i;
long g3options;
long rowsperstrip;
unsigned short compression;
unsigned short extra_samples[1];
int alpha;
short predictor;
short photometric;
short samplesperpixel;
short bitspersample;
int bytesperrow;
guchar *t, *src, *data;
guchar *cmap;
int colors;
int success;
GDrawable *drawable;
GDrawableType drawable_type;
GPixelRgn pixel_rgn;
int tile_height;
int y, yend;
char *name;
compression = tsvals.compression;
g3options = 0;
predictor = 0;
rowsperstrip = 0;
TIFFSetWarningHandler (tiff_warning);
TIFFSetErrorHandler (tiff_error);
tif = TIFFOpen (filename, "w");
if (!tif)
{
g_print ("Can't write image to\n%s", filename);
return 0;
}
name = g_strdup_printf( _("Saving %s:"), filename);
gimp_progress_init (name);
free (name);
drawable = gimp_drawable_get (layer);
drawable_type = gimp_drawable_type (layer);
gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0, drawable->width, drawable->height, FALSE, FALSE);
cols = drawable->width;
rows = drawable->height;
switch (drawable_type)
{
case RGB_IMAGE:
predictor = 2;
samplesperpixel = 3;
bitspersample = 8;
photometric = PHOTOMETRIC_RGB;
bytesperrow = cols * 3;
alpha = 0;
break;
case GRAY_IMAGE:
samplesperpixel = 1;
bitspersample = 8;
photometric = PHOTOMETRIC_MINISBLACK;
bytesperrow = cols;
alpha = 0;
break;
case RGBA_IMAGE:
predictor = 2;
samplesperpixel = 4;
bitspersample = 8;
photometric = PHOTOMETRIC_RGB;
bytesperrow = cols * 4;
alpha = 1;
break;
case GRAYA_IMAGE:
samplesperpixel = 2;
bitspersample = 8;
photometric = PHOTOMETRIC_MINISBLACK;
bytesperrow = cols * 2;
alpha = 1;
break;
case INDEXED_IMAGE:
samplesperpixel = 1;
bitspersample = 8;
photometric = PHOTOMETRIC_PALETTE;
bytesperrow = cols;
alpha = 0;
cmap = gimp_image_get_cmap (image, &colors);
for (i = 0; i < colors; i++)
{
red[i] = *cmap++ * 65535 / 255;
grn[i] = *cmap++ * 65535 / 255;
blu[i] = *cmap++ * 65535 / 255;
}
break;
case INDEXEDA_IMAGE:
return 0;
default:
return 0;
}
if (rowsperstrip == 0)
rowsperstrip = (8 * 1024) / bytesperrow;
if (rowsperstrip == 0)
rowsperstrip = 1;
/* Set TIFF parameters. */
TIFFSetField (tif, TIFFTAG_SUBFILETYPE, 0);
TIFFSetField (tif, TIFFTAG_IMAGEWIDTH, cols);
TIFFSetField (tif, TIFFTAG_IMAGELENGTH, rows);
TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, bitspersample);
TIFFSetField (tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
TIFFSetField (tif, TIFFTAG_COMPRESSION, compression);
if ((compression == COMPRESSION_LZW) && (predictor != 0))
TIFFSetField (tif, TIFFTAG_PREDICTOR, predictor);
if (alpha)
{
extra_samples [0] = EXTRASAMPLE_ASSOCALPHA;
TIFFSetField (tif, TIFFTAG_EXTRASAMPLES, 1, extra_samples);
}
TIFFSetField (tif, TIFFTAG_PHOTOMETRIC, photometric);
TIFFSetField (tif, TIFFTAG_DOCUMENTNAME, filename);
TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel);
TIFFSetField (tif, TIFFTAG_ROWSPERSTRIP, rowsperstrip);
/* TIFFSetField( tif, TIFFTAG_STRIPBYTECOUNTS, rows / rowsperstrip ); */
TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
#ifdef GIMP_HAVE_RESOLUTION_INFO
/* resolution fields */
{
gdouble xresolution;
gdouble yresolution;
gushort save_unit = RESUNIT_INCH;
GimpUnit unit;
gfloat factor;
gimp_image_get_resolution (orig_image, &xresolution, &yresolution);
unit = gimp_image_get_unit (orig_image);
factor = gimp_unit_get_factor (unit);
/* if we have a metric unit, save the resolution as centimeters
*/
if ((ABS (factor - 0.0254) < 1e-5) || /* m */
(ABS (factor - 0.254) < 1e-5) || /* dm */
(ABS (factor - 2.54) < 1e-5) || /* cm */
(ABS (factor - 25.4) < 1e-5)) /* mm */
{
save_unit = RESUNIT_CENTIMETER;
xresolution /= 2.54;
yresolution /= 2.54;
}
if (xresolution > 1e-5 && yresolution > 1e-5)
{
TIFFSetField (tif, TIFFTAG_XRESOLUTION, xresolution);
TIFFSetField (tif, TIFFTAG_YRESOLUTION, yresolution);
TIFFSetField (tif, TIFFTAG_RESOLUTIONUNIT, save_unit);
}
}
#endif /* GIMP_HAVE_RESOLUTION_INFO */
/* do we have a comment? If so, create a new parasite to hold it,
* and attach it to the image. The attach function automatically
* detaches a previous incarnation of the parasite. */
#ifdef GIMP_HAVE_PARASITES
if (image_comment && *image_comment != '\000')
{
Parasite *parasite;
TIFFSetField (tif, TIFFTAG_IMAGEDESCRIPTION, image_comment);
parasite = parasite_new ("gimp-comment", 1,
strlen (image_comment) + 1, image_comment);
gimp_image_parasite_attach (orig_image, parasite);
parasite_free (parasite);
}
#endif /* GIMP_HAVE_PARASITES */
if (drawable_type == INDEXED_IMAGE)
TIFFSetField (tif, TIFFTAG_COLORMAP, red, grn, blu);
/* array to rearrange data */
tile_height = gimp_tile_height ();
src = g_new (guchar, bytesperrow * tile_height);
data = g_new (guchar, bytesperrow);
/* Now write the TIFF data. */
for (y = 0; y < rows; y = yend)
{
yend = y + tile_height;
yend = MIN (yend, rows);
gimp_pixel_rgn_get_rect (&pixel_rgn, src, 0, y, cols, yend - y);
for (row = y; row < yend; row++)
{
t = src + bytesperrow * (row - y);
switch (drawable_type)
{
case INDEXED_IMAGE:
success = (TIFFWriteScanline (tif, t, row, 0) >= 0);
break;
case GRAY_IMAGE:
success = (TIFFWriteScanline (tif, t, row, 0) >= 0);
break;
case GRAYA_IMAGE:
for (col = 0; col < cols*samplesperpixel; col+=samplesperpixel)
{
/* pre-multiply gray by alpha */
data[col + 0] = (t[col + 0] * t[col + 1]) / 255;
data[col + 1] = t[col + 1]; /* alpha channel */
}
success = (TIFFWriteScanline (tif, data, row, 0) >= 0);
break;
case RGB_IMAGE:
success = (TIFFWriteScanline (tif, t, row, 0) >= 0);
break;
case RGBA_IMAGE:
for (col = 0; col < cols*samplesperpixel; col+=samplesperpixel)
{
/* pre-multiply rgb by alpha */
data[col+0] = t[col + 0] * t[col + 3] / 255;
data[col+1] = t[col + 1] * t[col + 3] / 255;
data[col+2] = t[col + 2] * t[col + 3] / 255;
data[col+3] = t[col + 3]; /* alpha channel */
}
success = (TIFFWriteScanline (tif, data, row, 0) >= 0);
break;
default:
success = FALSE;
break;
}
if (!success) {
g_message("TIFF Failed a scanline write on row %d", row);
return 0;
}
}
gimp_progress_update ((double) row / (double) rows);
}
TIFFFlushData (tif);
TIFFClose (tif);
gimp_drawable_detach (drawable);
g_free (data);
return 1;
}
static void
init_gtk (void)
{
gchar **argv;
gint argc;
argc = 1;
argv = g_new (gchar *, 1);
argv[0] = g_strdup ("tiff");
gtk_init (&argc, &argv);
gtk_rc_parse (gimp_gtkrc ());
}
static gint
save_dialog (void)
{
GtkWidget *dlg;
GtkWidget *vbox;
GtkWidget *frame;
GtkWidget *hbox;
GtkWidget *label;
GtkWidget *entry;
dlg = gimp_dialog_new ( _("Save as TIFF"), "tiff",
gimp_plugin_help_func, "filters/tiff.html",
GTK_WIN_POS_MOUSE,
FALSE, TRUE, FALSE,
_("OK"), save_ok_callback,
NULL, NULL, NULL, TRUE, FALSE,
_("Cancel"), gtk_widget_destroy,
NULL, 1, NULL, FALSE, TRUE,
NULL);
gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
GTK_SIGNAL_FUNC (gtk_main_quit),
NULL);
vbox = gtk_vbox_new (FALSE, 4);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), vbox, FALSE, TRUE, 0);
/* compression */
frame =
gimp_radio_group_new2 (TRUE, _("Compression"),
gimp_radio_button_update,
&tsvals.compression, (gpointer) tsvals.compression,
_("None"), (gpointer) COMPRESSION_NONE, NULL,
_("LZW"), (gpointer) COMPRESSION_LZW, NULL,
_("Pack Bits"), (gpointer) COMPRESSION_PACKBITS, NULL,
NULL);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
gtk_widget_show (frame);
/* comment entry */
hbox = gtk_hbox_new (FALSE, 4);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
gtk_widget_show (hbox);
label = gtk_label_new ( _("Comment:"));
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
gtk_widget_show (label);
entry = gtk_entry_new ();
gtk_widget_show (entry);
gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
gtk_entry_set_text (GTK_ENTRY (entry), image_comment);
gtk_signal_connect (GTK_OBJECT (entry), "changed",
GTK_SIGNAL_FUNC (comment_entry_callback),
NULL);
gtk_widget_show (frame);
gtk_widget_show (vbox);
gtk_widget_show (dlg);
gtk_main ();
gdk_flush ();
return tsint.run;
}
static void
save_ok_callback (GtkWidget *widget,
gpointer data)
{
tsint.run = TRUE;
gtk_widget_destroy (GTK_WIDGET (data));
}
static void
comment_entry_callback (GtkWidget *widget,
gpointer data)
{
gint len;
gchar *text;
text = gtk_entry_get_text (GTK_ENTRY (widget));
len = strlen (text);
/* Temporary kludge for overlength strings - just return */
if (len > 240)
{
g_message ("TIFF save: Your comment string is too long.\n");
return;
}
g_free (image_comment);
image_comment = g_strdup (text);
/* g_print ("COMMENT: %s\n", image_comment); */
}