From 7db03f7aab31cc04b1735fbda0df1eb2925cf42e Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Wed, 2 Jan 2019 18:47:57 +0100 Subject: [PATCH] TIFF support --- src/Makefile | 8 +- src/const.h | 1 + src/fileformats.h | 7 + src/loadsave.c | 4 + src/tifformat.c | 464 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 480 insertions(+), 4 deletions(-) create mode 100644 src/tifformat.c diff --git a/src/Makefile b/src/Makefile index c54bd00f..f20e621e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -461,10 +461,10 @@ endif LOPT += -lSDLmain -lSDL $(shell $(CROSS_SDLCONFIG) --libs) -lSDL_image endif ifeq (,$(CROSS_PKGCONFIG)) - COPT += -D__no_pnglib__ + COPT += -D__no_pnglib__ -D__no_tifflib__ else COPT += $(shell PKG_CONFIG_LIBDIR=$(CROSS_PKG_CONFIG_PATH) $(CROSS_PKGCONFIG) --cflags libpng || echo "-D__no_pnglib__" ) - LOPT += $(shell PKG_CONFIG_LIBDIR=$(CROSS_PKG_CONFIG_PATH) $(CROSS_PKGCONFIG) --libs libpng) + LOPT += $(shell PKG_CONFIG_LIBDIR=$(CROSS_PKG_CONFIG_PATH) $(CROSS_PKGCONFIG) --libs libpng libtiff-4) endif LUALOPT = -llua COPT += $(LUACOPT) @@ -576,7 +576,7 @@ endif COPT += -DNO_X11 endif LOPT += $(TTFLOPT) - LOPT += $(shell pkg-config --libs libpng) + LOPT += $(shell pkg-config --libs libpng libtiff-4) LOPT += $(LUALOPT) OBJDIR = ../obj/unix FCLOPT = -lfontconfig @@ -705,7 +705,7 @@ OBJS = main.o init.o graph.o $(APIOBJ) misc.o special.o \ fileformats.o miscfileformats.o libraw2crtc.o \ brush_ops.o buttons_effects.o layers.o \ oldies.o tiles.o colorred.o unicode.o gfx2surface.o \ - gfx2log.o + gfx2log.o tifformat.o ifndef NORECOIL OBJS += loadrecoil.o recoil.o endif diff --git a/src/const.h b/src/const.h index 87657e16..16ff57e0 100644 --- a/src/const.h +++ b/src/const.h @@ -153,6 +153,7 @@ enum FILE_FORMATS FORMAT_FLI, ///< Autodesk Animator FLI/FLC FORMAT_MOTO, ///< Thomson MO/TO computers pictures FORMAT_HGR, ///< Apple II HGR and DHGR + FORMAT_TIFF, ///< Tagged Image File Format FORMAT_MISC, ///< Must be last of enum: others formats recognized by SDL_image (or recoil) FORMAT_CLIPBOARD ///< To load/save from/to Clipboard }; diff --git a/src/fileformats.h b/src/fileformats.h index e9c67107..a510eb32 100644 --- a/src/fileformats.h +++ b/src/fileformats.h @@ -162,5 +162,12 @@ void Test_HGR(T_IO_Context *, FILE *); void Load_HGR(T_IO_Context *); void Save_HGR(T_IO_Context *); +// -- TIFF ------------------------------------------------------------------ +#ifndef __no_tifflib__ +void Test_TIFF(T_IO_Context *, FILE *); +void Load_TIFF(T_IO_Context *); +void Save_TIFF(T_IO_Context *); +#endif + /// @} #endif diff --git a/src/loadsave.c b/src/loadsave.c index 8037ad73..b6b9d8e3 100644 --- a/src/loadsave.c +++ b/src/loadsave.c @@ -2,6 +2,7 @@ */ /* Grafx2 - The Ultimate 256-color bitmap paint program + Copyright 2018 Thomas Bernard Copyright 2011 Pawel Góralski Copyright 2010 Alexander Filyanov Copyright 2009 Petter Lindquist @@ -143,6 +144,9 @@ const T_Format File_formats[] = { {FORMAT_FLI, " flc", Test_FLI, Load_FLI, NULL, 0, 0, 0, "flc", "flc;fli;dat"}, {FORMAT_MOTO," moto",Test_MOTO,Load_MOTO,Save_MOTO,0, 1, 0, "bin", "bin;map"}, {FORMAT_HGR, " hgr", Test_HGR, Load_HGR, Save_HGR, 0, 0, 1, "hgr", "hgr;dhgr;bin"}, +#ifndef __no_tifflib__ + {FORMAT_TIFF," tiff",Test_TIFF,Load_TIFF,Save_TIFF,0, 1, 1, "tif", "tif;tiff"}, +#endif {FORMAT_MISC,"misc.",NULL, NULL, NULL, 0, 0, 0, "", "tga;pnm;xpm;xcf;jpg;jpeg;tif;tiff"}, }; diff --git a/src/tifformat.c b/src/tifformat.c new file mode 100644 index 00000000..8a800243 --- /dev/null +++ b/src/tifformat.c @@ -0,0 +1,464 @@ +/* vim:expandtab:ts=2 sw=2: +*/ +/* Grafx2 - The Ultimate 256-color bitmap paint program + + Copyright 2018 Thomas Bernard + Copyright 1996-2001 Sunset Design (Guillaume Dorme & Karl Maritaud) + + Grafx2 is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 + of the License. + + Grafx2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grafx2; if not, see +*/ + +///@file tifformat.c +/// Support of TIFF +/// +/// @todo use TIFFSetErrorHandler() and TIFFSetWarningHandler() to +/// redirect warning/error output to our own functions + +#ifndef __no_tifflib__ + +#include +#include "global.h" +#include "io.h" +#include "loadsave.h" +#include "gfx2log.h" + +extern char Program_version[]; // generated in pversion.c +extern const char SVN_revision[]; // generated in version.c + +/// test for a valid TIFF +void Test_TIFF(T_IO_Context * context, FILE * file) +{ + char buffer[4]; + + File_error = 1; + if (!Read_bytes(file, buffer, 4)) + return; + if (0 == memcmp(buffer, "MM\0*", 4) || 0 == memcmp(buffer, "II*\0", 4)) + File_error = 0; +} + +/// Load current image in TIFF +static void Load_TIFF_image(T_IO_Context * context, TIFF * tif, word spp, word bps) +{ + tsize_t size; + int x, y; + unsigned int i, j; + + if (TIFFIsTiled(tif)) + { + // Tiled image + dword tile_width, tile_height; + if (!TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width) || !TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height)) + { + File_error = 2; + return; + } + if (spp > 1 || bps > 8) + { + dword * buffer; + dword x2, y2; + + buffer = malloc(sizeof(dword) * tile_width * tile_height); + for (y = 0; y < context->Height; y += tile_height) + { + for (x = 0; x < context->Width; x += tile_width) + { + if (!TIFFReadRGBATile(tif, x, y, buffer)) + { + free(buffer); + File_error = 2; + return; + } + j = 0; + // Note that the raster is assume to be organized such that the pixel at + // location (x,y) is raster[y*width+x]; with the raster origin in the + // lower-left hand corner of the tile. That is bottom to top organization. + // Edge tiles which partly fall off the image will be filled out with + // appropriate zeroed areas. + for (y2 = 0; y2 < tile_height; y2++) + { + int y_pos = y + tile_height - 1 - y2; + for (x2 = 0; x2 < tile_width ; x2++) + { + Set_pixel_24b(context, x + x2, y_pos, TIFFGetR(buffer[j]), TIFFGetG(buffer[j]), TIFFGetB(buffer[j])); + j++; + } + } + } + } + free(buffer); + } + else + { + byte * buffer; + //ttile_t tile; + + size = TIFFTileSize(tif); + buffer = malloc(size); + for (y = 0; y < context->Height; y += tile_height) + { + for (x = 0; x < context->Width; x += tile_width) + { + dword x2, y2; + if (TIFFReadTile(tif, buffer, x, y, 0, 0) == -1) + { + free(buffer); + File_error = 2; + return; + } + j = 0; + for (y2 = 0; y2 < tile_height; y2++) + { + for (x2 = 0; x2 < tile_width ; x2++) + { + Set_pixel(context, x + x2, y + y2, buffer[j]); + j++; + } + } + } + } + free(buffer); + } + } + else + { + dword rows_per_strip = 0; + tstrip_t strip, strip_count; + // "Striped" image + TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); + if (rows_per_strip == 0) + { + File_error = 2; + return; + } + if (spp > 1 || bps > 8) + { + // if not 8bit with colormap, use TIFFReadRGBAStrip + dword * buffer; + + strip_count = (context->Height + rows_per_strip - 1) / rows_per_strip; + buffer = malloc(sizeof(dword) * rows_per_strip * context->Width); + for (strip = 0, y = 0; strip < strip_count; strip++) + { + if (!TIFFReadRGBAStrip(tif, strip * rows_per_strip, buffer)) + { + free(buffer); + File_error = 2; + return; + } + // Note that the raster is assume to be organized such that the + // pixel at location (x,y) is raster[y*width+x]; with the raster + // origin in the lower-left hand corner of the strip. That is bottom + // to top organization. When reading a partial last strip in the + // file the last line of the image will begin at the beginning of + // the buffer. + y = (strip + 1) * rows_per_strip - 1; + if (y >= context->Height) + y = context->Height - 1; + for (i = 0, j = 0; + i < rows_per_strip && y >= (int)(strip * rows_per_strip); + i++, y--) + { + for (x = 0; x < context->Width; x++) + { + Set_pixel_24b(context, x, y, TIFFGetR(buffer[j]), TIFFGetG(buffer[j]), TIFFGetB(buffer[j])); + j++; + } + } + } + free(buffer); + return; // ignore next image for now... + } + else + { + byte * buffer = NULL; + + strip_count = TIFFNumberOfStrips(tif); + size = TIFFStripSize(tif); + GFX2_Log(GFX2_DEBUG, "TIFF %u strips of %u bytes\n", strip_count, size); + buffer = malloc(size); + for (strip = 0, y = 0; strip < strip_count; strip++) + { + tsize_t r = TIFFReadEncodedStrip(tif, strip, buffer, size); + if (r == -1) + { + free(buffer); + File_error = 2; + return; + } + for (i = 0, j = 0; i < rows_per_strip && y < context->Height; i++, y++) + { + for (x = 0; x < context->Width; x++) + { + switch (bps) + { + case 8: + Set_pixel(context, x, y, buffer[j++]); + break; + case 6: // 3 bytes => 4 pixels + Set_pixel(context, x++, y, buffer[j] >> 2); + if (x < context->Width) + { + Set_pixel(context, x++, y, (buffer[j] & 3) << 4 | (buffer[j+1] & 0xf0) >> 4); + j++; + if (x < context->Width) + { + Set_pixel(context, x++, y, (buffer[j] & 0x0f) << 2 | (buffer[j+1] & 0xc0) >> 6); + j++; + Set_pixel(context, x, y, buffer[j] & 0x3f); + } + } + j++; + break; + case 4: + Set_pixel(context, x++, y, buffer[j] >> 4); + Set_pixel(context, x, y, buffer[j++] & 0x0f); + break; + case 2: + Set_pixel(context, x++, y, buffer[j] >> 6); + Set_pixel(context, x++, y, (buffer[j] >> 4) & 3); + Set_pixel(context, x++, y, (buffer[j] >> 2) & 3); + Set_pixel(context, x, y, buffer[j++] & 3); + break; + case 1: + Set_pixel(context, x++, y, (buffer[j] >> 7) & 1); + Set_pixel(context, x++, y, (buffer[j] >> 6) & 1); + Set_pixel(context, x++, y, (buffer[j] >> 5) & 1); + Set_pixel(context, x++, y, (buffer[j] >> 4) & 1); + Set_pixel(context, x++, y, (buffer[j] >> 3) & 1); + Set_pixel(context, x++, y, (buffer[j] >> 2) & 1); + Set_pixel(context, x++, y, (buffer[j] >> 1) & 1); + Set_pixel(context, x, y, buffer[j++] & 1); + break; + default: + File_error = 2; + GFX2_Log(GFX2_ERROR, "TIFF : %u bps unsupported\n", bps); + free(buffer); + return; + } + } + } + } + free(buffer); + } + } +} + + +/// Load TIFF +void Load_TIFF_Sub(T_IO_Context * context, TIFF * tif, unsigned long file_size) +{ + int layer = 0; + enum PIXEL_RATIO ratio = PIXEL_SIMPLE; + dword width, height; + word bps, spp; + word photometric = PHOTOMETRIC_RGB; + char * desc; + + TIFFPrintDirectory(tif, stdout, TIFFPRINT_NONE); + if (!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width) || !TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height)) + return; + if (!TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bps) || !TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &spp)) + return; + GFX2_Log(GFX2_DEBUG, "TIFF #0 : %ux%u %ux%ubps\n", width, height, spp, bps); + if (TIFFGetField(tif, TIFFTAG_IMAGEDESCRIPTION, &desc)) + { + size_t len = strlen(desc); + if (len <= COMMENT_SIZE) + memcpy(context->Comment, desc, len + 1); + else + { + memcpy(context->Comment, desc, COMMENT_SIZE); + context->Comment[COMMENT_SIZE] = '\0'; + } + } + TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric); + File_error = 0; + Pre_load(context, width, height, file_size, FORMAT_TIFF, ratio, bps * spp); + if (File_error != 0) + return; + if (spp == 1) + { + struct { + word * r; + word * g; + word * b; + } colormap; + unsigned i, count; + + count = (bps <= 8) ? 1 << bps : 256; + if (TIFFGetField(tif, TIFFTAG_COLORMAP, &colormap.r, &colormap.g, &colormap.b)) + { + for (i = 0; i < count; i++) + { + context->Palette[i].R = colormap.r[i] >> 8; + context->Palette[i].G = colormap.g[i] >> 8; + context->Palette[i].B = colormap.b[i] >> 8; + } + } + else + { + // Grayscale palette + for (i = 0; i < count; i++) + { + unsigned int value = 255 + * (photometric == PHOTOMETRIC_MINISWHITE ? (count - 1 - i) : i) + / (count - 1); + context->Palette[i].R = value; + context->Palette[i].G = value; + context->Palette[i].B = value; + } + } + } + + for (;;) + { + Load_TIFF_image(context, tif, spp, bps); + if (File_error != 0) + return; + + if (context->Type != CONTEXT_MAIN_IMAGE) + return; + if (!TIFFReadDirectory(tif)) + return; + if (!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width) || !TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height)) + return; + if (!TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bps) || !TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &spp)) + return; + layer++; + GFX2_Log(GFX2_DEBUG, "TIFF #%d : %ux%u %ux%ubps\n", layer, width, height, spp, bps); + if ((int)width != context->Width || (int)height != context->Height) + return; + if (context->bpp != (spp * bps)) + return; + Set_loading_layer(context, layer); + TIFFPrintDirectory(tif, stdout, TIFFPRINT_NONE); + } +} + +/// Load TIFF from file +void Load_TIFF(T_IO_Context * context) +{ + FILE * file; + TIFF * tif; + + File_error = 1; + + file = Open_file_read(context); + if (file != NULL) + { + tif = TIFFFdOpen(fileno(file), context->File_name, "r"); + if (tif != NULL) + { + Load_TIFF_Sub(context, tif, File_length_file(file)); + TIFFClose(tif); + } + fclose(file); + } +} + + +void Save_TIFF_Sub(T_IO_Context * context, TIFF * tif) +{ + char version[64]; + int i; + int layer = 0; + dword width, height; + dword y; + tstrip_t strip; + struct { + word r[256]; + word g[256]; + word b[256]; + } colormap; + const word bps = 8; + const word spp = 1; + const dword rowsperstrip = 64; + + snprintf(version, sizeof(version), "GrafX2 %s.%s", Program_version, SVN_revision); + width = context->Width; + height = context->Height; + for (i = 0; i < 256; i++) + { + colormap.r[i] = 0x101 * context->Palette[i].R; + colormap.g[i] = 0x101 * context->Palette[i].G; + colormap.b[i] = 0x101 * context->Palette[i].B; + } + + for (;;) + { + GFX2_Log(GFX2_DEBUG, "TIFF save layer #%d\n", layer); + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bps); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, spp); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_LZW); + //TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); // not relevant if SAMPLESPERPIXEL == 1 + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, rowsperstrip); + if (context->Comment[0] != '\0') + TIFFSetField(tif, TIFFTAG_IMAGEDESCRIPTION, context->Comment); + TIFFSetField(tif, TIFFTAG_SOFTWARE, version); + TIFFSetField(tif, TIFFTAG_COLORMAP, colormap.r, colormap.g, colormap.b); + +#if 0 + for (y = 0; y < height; y++) + { + if (TIFFWriteScanline(tif, context->Target_address + y*context->Pitch, y, 0) < 0) + return; + } +#else + for (y = 0, strip = 0; y < height; y += rowsperstrip, strip++) + { + if (TIFFWriteEncodedStrip(tif, strip, context->Target_address + y*context->Pitch, rowsperstrip * context->Pitch) < 0) + return; + } +#endif + layer++; + + if (layer >= context->Nb_layers) + { + TIFFFlushData(tif); + File_error = 0; // everything was fine + return; + } + Set_saving_layer(context, layer); + if (!TIFFWriteDirectory(tif)) + return; + TIFFFlushData(tif); + } +} + +/// Save TIFF +void Save_TIFF(T_IO_Context * context) +{ + TIFF * tif; + + File_error = 1; + +#if defined(WIN32) + if (context->File_name_unicode != NULL && context->File_name_unicode[0] != 0) + tif = TIFFOpenW(context->File_name_unicode, "w"); + else +#endif + tif = TIFFOpen(context->File_name, "w"); + if (tif != NULL) + { + Save_TIFF_Sub(context, tif); + TIFFClose(tif); + } +} + +#endif