#include "config.h" #include "Imlib2_Loader.h" #include #include #define DBG_PFX "LDR-tiff" static const char *const _formats[] = { "tiff", "tif" }; #define DD(fmt...) DC(DBG_PFX, 0x80, fmt) static void _tiff_error(const char *module, const char *fmt, va_list ap) { #if IMLIB2_DEBUG char buf[128]; vsnprintf(buf, sizeof(buf), fmt, ap); D("%s: %s: %s\n", __func__, module, buf); #endif } static struct { const unsigned char *data, *dptr; unsigned int size; } mdata; static void mm_init(const void *src, unsigned int size) { mdata.data = mdata.dptr = src; mdata.size = size; } static tmsize_t _tiff_read(thandle_t ctx, void *buf, tmsize_t len) { DD("%s: len=%ld\n", __func__, (long)len); if (mdata.dptr + len > mdata.data + mdata.size) return 0; /* Out of data */ memcpy(buf, mdata.dptr, len); mdata.dptr += len; return len; } static tmsize_t _tiff_write(thandle_t ctx, void *buf, tmsize_t len) { DD("%s: len=%ld\n", __func__, (long)len); return 0; } static toff_t _tiff_seek(thandle_t ctx, toff_t offs, int whence) { const unsigned char *dptr; DD("%s: offs=%ld, whence=%d\n", __func__, (long)offs, whence); switch (whence) { default: return -1; case SEEK_SET: dptr = mdata.data + offs; break; case SEEK_CUR: dptr = mdata.data += offs; break; case SEEK_END: dptr = mdata.data + mdata.size + offs; break; } if (dptr > mdata.data + mdata.size) return -1; /* Out of data */ mdata.dptr = dptr; return mdata.dptr - mdata.data; } static int _tiff_close(thandle_t ctx) { DD("%s\n", __func__); return 0; } static toff_t _tiff_size(thandle_t ctx) { DD("%s: size=%d\n", __func__, mdata.size); return mdata.size; } static int _tiff_map(thandle_t ctx, void **base, toff_t *size) { DD("%s\n", __func__); *base = (void *)mdata.data; *size = mdata.size; return 1; } static void _tiff_unmap(thandle_t ctx, void *base, toff_t size) { DD("%s\n", __func__); } /* This is a wrapper data structure for TIFFRGBAImage, so that data can be */ /* passed into the callbacks. More elegent, I think, than a bunch of globals */ typedef struct { TIFFRGBAImage rgba; tileContigRoutine put_contig; tileSeparateRoutine put_separate; ImlibImage *image; } TIFFRGBAImage_Extra; #define PIM(_x, _y) buffer + ((_x) + image_width * (_y)) static void raster(TIFFRGBAImage_Extra *img, uint32_t *rast, uint32_t x, uint32_t y, uint32_t w, uint32_t h) { uint32_t image_width, image_height; uint32_t *pixel, pixel_value; uint32_t i, j, k; uint32_t *buffer_pixel, *buffer = img->image->data; int alpha_premult; int a, r, g, b; image_width = img->image->w; image_height = img->image->h; #if 0 printf("%s: x,y=%d,%d wxh=%dx%d (image %dx%d)\n", __func__, x, y, w, h, image_width, image_height); #endif /* rast seems to point to the beginning of the last strip processed */ /* so you need use negative offsets. Bizzare. Someone please check this */ /* I don't understand why, but that seems to be what's going on. */ /* libtiff needs better docs! */ alpha_premult = img->rgba.alpha == EXTRASAMPLE_UNASSALPHA; switch (img->rgba.orientation) { default: case ORIENTATION_TOPLEFT: case ORIENTATION_TOPRIGHT: for (j = 0; j < h; j++) { pixel = rast - j * image_width; for (i = 0; i < w; i++) { pixel_value = *pixel++; a = TIFFGetA(pixel_value); r = TIFFGetR(pixel_value); g = TIFFGetG(pixel_value); b = TIFFGetB(pixel_value); if ((a > 0) && (a < 255) && (alpha_premult)) { r = (r * 255) / a; g = (g * 255) / a; b = (b * 255) / a; } k = x + i; if (img->rgba.orientation == ORIENTATION_TOPRIGHT) k = image_width - 1 - k; buffer_pixel = PIM(k, image_height - 1 - (y - j)); *buffer_pixel = PIXEL_ARGB(a, r, g, b); } } break; case ORIENTATION_BOTRIGHT: case ORIENTATION_BOTLEFT: for (j = 0; j < h; j++) { pixel = rast + j * image_width; for (i = 0; i < w; i++) { pixel_value = *pixel++; a = TIFFGetA(pixel_value); r = TIFFGetR(pixel_value); g = TIFFGetG(pixel_value); b = TIFFGetB(pixel_value); if ((a > 0) && (a < 255) && (alpha_premult)) { r = (r * 255) / a; g = (g * 255) / a; b = (b * 255) / a; } k = x + i; if (img->rgba.orientation == ORIENTATION_BOTRIGHT) k = image_width - 1 - k; buffer_pixel = PIM(k, image_height - 1 - (y + j)); *buffer_pixel = PIXEL_ARGB(a, r, g, b); } } break; case ORIENTATION_LEFTTOP: case ORIENTATION_RIGHTTOP: for (i = 0; i < h; i++) { pixel = rast - i * image_height; for (j = 0; j < w; j++) { pixel_value = *pixel++; a = TIFFGetA(pixel_value); r = TIFFGetR(pixel_value); g = TIFFGetG(pixel_value); b = TIFFGetB(pixel_value); if ((a > 0) && (a < 255) && (alpha_premult)) { r = (r * 255) / a; g = (g * 255) / a; b = (b * 255) / a; } k = y - i; if (img->rgba.orientation == ORIENTATION_LEFTTOP) k = image_width - 1 - k; buffer_pixel = PIM(k, x + j); *buffer_pixel = PIXEL_ARGB(a, r, g, b); } } break; case ORIENTATION_RIGHTBOT: case ORIENTATION_LEFTBOT: for (i = 0; i < h; i++) { pixel = rast + i * image_height; for (j = 0; j < w; j++) { pixel_value = *pixel++; a = TIFFGetA(pixel_value); r = TIFFGetR(pixel_value); g = TIFFGetG(pixel_value); b = TIFFGetB(pixel_value); if ((a > 0) && (a < 255) && (alpha_premult)) { r = (r * 255) / a; g = (g * 255) / a; b = (b * 255) / a; } k = y + i; if (img->rgba.orientation == ORIENTATION_RIGHTBOT) k = image_width - 1 - k; buffer_pixel = PIM(k, image_height - 1 - (x + j)); *buffer_pixel = PIXEL_ARGB(a, r, g, b); } } break; } if (img->image->lc) { /* for tile based images, we just progress each tile because */ /* of laziness. Couldn't think of a good way to do this */ switch (img->rgba.orientation) { default: case ORIENTATION_TOPLEFT: if (w >= image_width) { __imlib_LoadProgressRows(img->image, image_height - y - 1, h); } else { /* for tile based images, we just progress each tile because */ /* of laziness. Couldn't think of a good way to do this */ y = image_height - 1 - y; goto progress_a; } break; case ORIENTATION_TOPRIGHT: y = image_height - 1 - y; goto progress_a; case ORIENTATION_BOTRIGHT: y = image_height - y - h; goto progress_a; case ORIENTATION_BOTLEFT: y = image_height - y - h; goto progress_a; progress_a: __imlib_LoadProgress(img->image, x, y, w, h); break; case ORIENTATION_LEFTTOP: y = image_width - 1 - y; goto progress_b; case ORIENTATION_RIGHTTOP: y = y + 1 - h; goto progress_b; case ORIENTATION_RIGHTBOT: y = image_width - y - h; goto progress_b; case ORIENTATION_LEFTBOT: goto progress_b; progress_b: __imlib_LoadProgress(img->image, y, x, h, w); break; } } } static void put_contig_and_raster(TIFFRGBAImage *img, uint32_t *rast, uint32_t x, uint32_t y, uint32_t w, uint32_t h, int32_t fromskew, int32_t toskew, unsigned char *cp) { ((TIFFRGBAImage_Extra *) img)->put_contig(img, rast, x, y, w, h, fromskew, toskew, cp); raster((TIFFRGBAImage_Extra *) img, rast, x, y, w, h); } static void put_separate_and_raster(TIFFRGBAImage *img, uint32_t *rast, uint32_t x, uint32_t y, uint32_t w, uint32_t h, int32_t fromskew, int32_t toskew, unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) { ((TIFFRGBAImage_Extra *) img)->put_separate(img, rast, x, y, w, h, fromskew, toskew, r, g, b, a); raster((TIFFRGBAImage_Extra *) img, rast, x, y, w, h); } static int _sig_check(const uint8_t *data, unsigned int size) { if (size < 8) return 1; if (data[0] == 'I' && data[1] == 'I') return !(data[2] == 42 && data[3] == 0); if (data[0] == 'M' && data[1] == 'M') return !(data[2] == 0 && data[3] == 42); return 1; } static int _load(ImlibImage *im, int load_data) { int rc; TIFF *tif = NULL; TIFFRGBAImage_Extra rgba_image; uint32_t *rast = NULL; char txt[1024]; rc = LOAD_FAIL; rgba_image.image = NULL; /* Signature check */ if (_sig_check(im->fi->fdata, im->fi->fsize)) goto quit; mm_init(im->fi->fdata, im->fi->fsize); TIFFSetErrorHandler(_tiff_error); TIFFSetWarningHandler(_tiff_error); tif = TIFFClientOpen(im->fi->name, "r", NULL, _tiff_read, _tiff_write, _tiff_seek, _tiff_close, _tiff_size, _tiff_map, _tiff_unmap); if (!tif) goto quit; strcpy(txt, "Cannot be processed by libtiff"); if (!TIFFRGBAImageOK(tif, txt)) goto quit; rc = LOAD_BADIMAGE; /* Format accepted */ strcpy(txt, "Cannot begin reading tiff"); if (!TIFFRGBAImageBegin((TIFFRGBAImage *) & rgba_image, tif, 1, txt)) goto quit; rgba_image.image = im; if (!rgba_image.rgba.put.any) { E("No put function\n"); goto quit; } switch (rgba_image.rgba.orientation) { default: case ORIENTATION_TOPLEFT: case ORIENTATION_TOPRIGHT: case ORIENTATION_BOTRIGHT: case ORIENTATION_BOTLEFT: im->w = rgba_image.rgba.width; im->h = rgba_image.rgba.height; break; case ORIENTATION_LEFTTOP: case ORIENTATION_RIGHTTOP: case ORIENTATION_RIGHTBOT: case ORIENTATION_LEFTBOT: im->w = rgba_image.rgba.height; im->h = rgba_image.rgba.width; break; } if (!IMAGE_DIMENSIONS_OK(im->w, im->h)) goto quit; im->has_alpha = rgba_image.rgba.alpha != EXTRASAMPLE_UNSPECIFIED; if (!load_data) QUIT_WITH_RC(LOAD_SUCCESS); /* Load data */ if (!__imlib_AllocateData(im)) QUIT_WITH_RC(LOAD_OOM); rast = _TIFFmalloc(sizeof(uint32_t) * im->w * im->h); if (!rast) QUIT_WITH_RC(LOAD_OOM); if (rgba_image.rgba.isContig) { rgba_image.put_contig = rgba_image.rgba.put.contig; rgba_image.rgba.put.contig = put_contig_and_raster; } else { rgba_image.put_separate = rgba_image.rgba.put.separate; rgba_image.rgba.put.separate = put_separate_and_raster; } if (!TIFFRGBAImageGet((TIFFRGBAImage *) & rgba_image, rast, rgba_image.rgba.width, rgba_image.rgba.height)) goto quit; rc = LOAD_SUCCESS; quit: if (rast) _TIFFfree(rast); if (rgba_image.image) TIFFRGBAImageEnd((TIFFRGBAImage *) & rgba_image); if (tif) TIFFClose(tif); return rc; } /* this seems to work, except the magic number isn't written. I'm guessing */ /* this is a problem in libtiff */ static int _save(ImlibImage *im) { int rc; TIFF *tif = NULL; uint8_t *buf = NULL; const uint32_t *imdata; uint32_t pixel; double alpha_factor; int x, y; uint8_t r, g, b, a = 0; int has_alpha = im->has_alpha; int compression_type; int i; ImlibImageTag *tag; TIFFSetErrorHandler(_tiff_error); TIFFSetWarningHandler(_tiff_error); tif = TIFFFdOpen(fileno(im->fi->fp), im->fi->name, "w"); if (!tif) return LOAD_BADFILE; rc = LOAD_BADFILE; /* None of the TIFFSetFields are checked for errors, but since they */ /* shouldn't fail, this shouldn't be a problem */ TIFFSetField(tif, TIFFTAG_IMAGELENGTH, im->h); TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, im->w); TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE); /* look for tags attached to image to get extra parameters like quality */ /* settings etc. - this is the "api" to hint for extra information for */ /* saver modules */ /* compression */ compression_type = COMPRESSION_ADOBE_DEFLATE; tag = __imlib_GetTag(im, "compression_type"); if (tag) { switch (tag->val) { default: break; case COMPRESSION_NONE: case COMPRESSION_CCITTRLE: case COMPRESSION_CCITTFAX3: case COMPRESSION_CCITTFAX4: case COMPRESSION_LZW: case COMPRESSION_OJPEG: case COMPRESSION_JPEG: case COMPRESSION_NEXT: case COMPRESSION_CCITTRLEW: case COMPRESSION_PACKBITS: case COMPRESSION_THUNDERSCAN: case COMPRESSION_IT8CTPAD: case COMPRESSION_IT8LW: case COMPRESSION_IT8MP: case COMPRESSION_IT8BL: case COMPRESSION_PIXARFILM: case COMPRESSION_PIXARLOG: case COMPRESSION_DEFLATE: case COMPRESSION_ADOBE_DEFLATE: case COMPRESSION_DCS: case COMPRESSION_JBIG: case COMPRESSION_SGILOG: case COMPRESSION_SGILOG24: compression_type = tag->val; break; } } TIFFSetField(tif, TIFFTAG_COMPRESSION, compression_type); if (has_alpha) { uint16_t extras[] = { EXTRASAMPLE_ASSOCALPHA }; TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 4); TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, 1, extras); } else { TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); } TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0)); buf = _TIFFmalloc(TIFFScanlineSize(tif)); if (!buf) QUIT_WITH_RC(LOAD_OOM); imdata = im->data; for (y = 0; y < im->h; y++) { i = 0; for (x = 0; x < im->w; x++) { pixel = imdata[(y * im->w) + x]; r = PIXEL_R(pixel); g = PIXEL_G(pixel); b = PIXEL_B(pixel); if (has_alpha) { /* TIFF makes you pre-mutiply the rgb components by alpha */ a = PIXEL_A(pixel); alpha_factor = ((double)a / 255.0); r *= alpha_factor; g *= alpha_factor; b *= alpha_factor; } /* This might be endian dependent */ buf[i++] = r; buf[i++] = g; buf[i++] = b; if (has_alpha) buf[i++] = a; } if (!TIFFWriteScanline(tif, buf, y, 0)) goto quit; if (im->lc && __imlib_LoadProgressRows(im, y, 1)) QUIT_WITH_RC(LOAD_BREAK); } rc = LOAD_SUCCESS; quit: if (buf) _TIFFfree(buf); if (tif) TIFFClose(tif); return rc; } IMLIB_LOADER(_formats, _load, _save);