diff options
author | Henning Makholm <henning@makholm.net> | 2006-01-27 18:00:00 (EST) |
---|---|---|
committer | Julien Jorge <julien.jorge@stuff-o-matic.com> | 2013-01-10 16:00:40 (EST) |
commit | 7b7cd6da61b1fcc0f2a3ecce2cb9e6c42782c717 (patch) | |
tree | 98d2772f50aaddb02ac94492d2b8b151aa3e9465 /xcf2png.c | |
download | xcftools-7b7cd6da61b1fcc0f2a3ecce2cb9e6c42782c717.zip xcftools-7b7cd6da61b1fcc0f2a3ecce2cb9e6c42782c717.tar.gz xcftools-7b7cd6da61b1fcc0f2a3ecce2cb9e6c42782c717.tar.bz2 |
Import of release 0.7
Diffstat (limited to 'xcf2png.c')
-rw-r--r-- | xcf2png.c | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/xcf2png.c b/xcf2png.c new file mode 100644 index 0000000..025b148 --- /dev/null +++ b/xcf2png.c @@ -0,0 +1,426 @@ +/* Convert xcf files to png + * + * Copyright (C) 2006 Henning Makholm + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "xcftools.h" +#include "flatten.h" +#include "palette.h" +#include <png.h> +#include <stdlib.h> +#include <string.h> +#include <locale.h> +#include <ctype.h> +#if HAVE_GETOPT_H +#include <getopt.h> +#else +#include <unistd.h> +#endif +#ifndef HAVE_GETOPT_LONG +#define getopt_long(argc,argv,optstring,l1,l2) getopt(argc,argv,optstring) +#endif + +#include "xcf2png.oi" + +static void +usage(FILE *where) +{ + fprintf(where,_("Usage: %s [options] filename.xcf[.gz] [layers]\n"), + progname) ; + fprintf(where,_("Options:\n")); + opt_usage(where); + if( where == stderr ) { + exit(1); + } +} + +static struct FlattenSpec flatspec ; + +static FILE *outfile = NULL ; +static png_structp libpng = NULL ; +static png_infop libpng2 = NULL ; + +static void +my_error_callback(png_structp png_ptr, png_const_charp errormsg) +{ + FatalUnexpected(_("Libpng error '%s'"),errormsg); +} + + +static void +init_output(void) +{ + int bit_depth ; + int color_type ; + int invert_mono = 0 ; + png_colorp pngpalette = NULL ; + png_bytep ptrans = NULL ; + + outfile = openout(flatspec.output_filename); + libpng = png_create_write_struct(PNG_LIBPNG_VER_STRING, + png_voidp_NULL, + my_error_callback, + png_error_ptr_NULL); + if( !libpng ) + FatalUnexpected(_("Couldn't initialize libpng library")); + + libpng2 = png_create_info_struct(libpng); + if( !libpng2 ) + FatalUnexpected("Couldn't create PNG info structure"); + + png_init_io(libpng,outfile); + + bit_depth = 8; + switch( flatspec.out_color_mode ) { + case COLOR_GRAY: + if( flatspec.default_pixel == PERHAPS_ALPHA_CHANNEL || + flatspec.default_pixel == FORCE_ALPHA_CHANNEL ) + color_type = PNG_COLOR_TYPE_GRAY_ALPHA ; + else + color_type = PNG_COLOR_TYPE_GRAY ; + break ; + case COLOR_RGB: + if( flatspec.default_pixel == PERHAPS_ALPHA_CHANNEL || + flatspec.default_pixel == FORCE_ALPHA_CHANNEL ) + color_type = PNG_COLOR_TYPE_RGB_ALPHA ; + else + color_type = PNG_COLOR_TYPE_RGB ; + break ; + case COLOR_INDEXED: + if( paletteSize == 2 && + palette[0] == NEWALPHA(0,255) && + palette[1] == NEWALPHA(-1,255) ) { + color_type = PNG_COLOR_TYPE_GRAY ; + bit_depth = 1 ; + } else if( paletteSize == 2 && + palette[0] == NEWALPHA(-1,255) && + palette[1] == NEWALPHA(0,255) ) { + color_type = PNG_COLOR_TYPE_GRAY ; + bit_depth = 1 ; + invert_mono = 1 ; + } else { + unsigned i ; + int need_trans = 0 ; + color_type = PNG_COLOR_TYPE_PALETTE ; + pngpalette = xcfmalloc(paletteSize*sizeof(png_color)) ; + ptrans = xcfmalloc(paletteSize); + for(i = 0; i<paletteSize; i++ ) { + pngpalette[i].red = 255 & (palette[i] >> RED_SHIFT); + pngpalette[i].green = 255 & (palette[i] >> GREEN_SHIFT); + pngpalette[i].blue = 255 & (palette[i] >> BLUE_SHIFT); + if( (ptrans[i] = ALPHA(palette[i])) != 255 ) + need_trans = 1 ; + } + if( !need_trans ) { + xcffree(ptrans); + ptrans = NULL ; + } + if( paletteSize <= 2 ) + bit_depth = 1 ; + else if( paletteSize <= 4 ) + bit_depth = 2 ; + else if( paletteSize <= 16 ) + bit_depth = 4 ; + else + bit_depth = 8; + } + break ; + default: + FatalUnexpected("This can't happen (unknown out_color_mode)"); + } + + if( verboseFlag ) { + fprintf(stderr,"Writing PNG: %s%s%s%s, %d bits", + color_type & PNG_COLOR_MASK_COLOR ? _("color") : _("grayscale"), + color_type & PNG_COLOR_MASK_PALETTE ? _("+palette") : "", + color_type & PNG_COLOR_MASK_ALPHA ? _("+alpha") : "", + ptrans || NULLALPHA(flatspec.default_pixel) + ? _("+transparency") : "", + bit_depth); + if( pngpalette ) + fprintf(stderr,_(" (%d colors)"),paletteSize); + fprintf(stderr,"\n"); + } + + png_set_IHDR(libpng,libpng2,flatspec.dim.width,flatspec.dim.height, + bit_depth, color_type, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + if( invert_mono ) + png_set_invert_mono(libpng); + + if( pngpalette ) + png_set_PLTE(libpng,libpng2,pngpalette,paletteSize); + if( ptrans ) + png_set_tRNS(libpng,libpng2,ptrans,paletteSize,NULL); + else if ( !pngpalette && + NULLALPHA(flatspec.default_pixel) ) { + static png_color_16 trans ; + trans.gray = + trans.red = 255 & (flatspec.default_pixel >> RED_SHIFT) ; + trans.green = 255 & (flatspec.default_pixel >> GREEN_SHIFT) ; + trans.blue = 255 & (flatspec.default_pixel >> BLUE_SHIFT) ; + png_set_tRNS(libpng,libpng2,NULL,0,&trans); + } + + /* png_set_text here */ + + png_write_info(libpng,libpng2); + + if( bit_depth < 8 ) + png_set_packing(libpng); + + switch( color_type ) { + case PNG_COLOR_TYPE_RGB: + case PNG_COLOR_TYPE_RGBA: +#if (BLUE_SHIFT < RED_SHIFT) == !defined(WORDS_BIGENDIAN) + png_set_bgr(libpng); +#endif + if( color_type == PNG_COLOR_TYPE_RGB ) +#if (ALPHA_SHIFT < RED_SHIFT) == !defined(WORDS_BIGENDIAN) + png_set_filler(libpng,0,PNG_FILLER_BEFORE); + else + png_set_swap_alpha(libpng); +#else + png_set_filler(libpng,0,PNG_FILLER_AFTER); +#endif + break ; + case PNG_COLOR_TYPE_GRAY: + png_set_filler(libpng,0,PNG_FILLER_AFTER); + break ; + case PNG_COLOR_TYPE_GRAY_ALPHA: + case PNG_COLOR_TYPE_PALETTE: + break ; + default: + FatalUnexpected("This can't happen (unexpected png color_type)"); + } +} + + +static void +raw_callback(unsigned num, rgba *pixels) { + if( libpng == NULL ) { + init_output() ; + } + png_write_row(libpng,(png_bytep)pixels); + xcffree(pixels); +} + +static void +graying_callback(unsigned num, rgba *pixels) { + png_bytep fillptr = (uint8_t *)pixels ; + unsigned i ; + for( i = 0 ; i < num ; i++ ) { + rgba pixel = pixels[i] ; + int g = degrayPixel(pixel) ; + if( g == -1 ) + FatalGeneric(103, + _("Grayscale output selected, but colored pixel(s) found")); + *fillptr++ = g ; + *fillptr++ = ALPHA(pixel) ; + } + raw_callback(num,pixels); +} + +static void +optimistic_palette_callback(unsigned num,rgba *pixels) { + unsigned prev_size = paletteSize ; + if( !palettify_row(pixels,num) || paletteSize != prev_size ) + FatalUnexpected("Oops! Somehow the precomputed palette does not suffice " + "after all..."); + raw_callback(num,pixels); +} + +static enum out_color_mode +guessIndexed(struct FlattenSpec *spec,rgba *allPixels[]) +{ + if( allPixels == NULL ) { + if (spec->gimpish_indexed && colormapLength ) { + unsigned i ; + init_palette_hash(); + for( i=0; i<colormapLength; i++ ) + lookup_or_intern(NEWALPHA(colormap[i],255)); + if( lookup_or_intern( FULLALPHA(spec->default_pixel) ? + spec->default_pixel : 0 ) >= 0 ) + return COLOR_INDEXED ; + } + } else { + init_palette_hash() ; + if( palettify_rows(allPixels,spec->dim.width,spec->dim.height) ) { + /* Might grayscale sometimes be preferred? No, that is what + * -g is for! */ + return COLOR_INDEXED ; + } + } + return COLOR_BY_CONTENTS ; +} + +static lineCallback +selectCallback(void) +{ + switch( flatspec.out_color_mode ) { + default: + case COLOR_RGB: return &raw_callback ; + case COLOR_GRAY: return &graying_callback ; + case COLOR_INDEXED: + if( flatspec.process_in_memory ) + return &raw_callback ; + else + return &optimistic_palette_callback ; + } +} + +/* findUnusedColor() will prefer to find a gray pixel */ +static rgba +findUnusedColor(rgba *pixels[],unsigned width,unsigned height) +{ + size_t freqtab[256] ; + unsigned x,y ; + unsigned i,j ; + rgba sofar ; + + for( i=0; i<256; i++ ) + freqtab[i] = 0 ; + for( y=0; y<height; y++ ) + for( x=0; x<width; x++ ) + if( pixels[y][x] ) + freqtab[255 & (pixels[y][x] >> RED_SHIFT)] ++ ; + j = 0 ; + for( i=0; i<256; i++ ) { + if( freqtab[i] == 0 ) { + return ((rgba)i << RED_SHIFT) + + ((rgba)i << GREEN_SHIFT) + + ((rgba)i << BLUE_SHIFT) + + ((rgba)255 << ALPHA_SHIFT) ; + } + if( freqtab[i] < freqtab[j] ) { + j = i ; + } + } + sofar = ((rgba)255<<ALPHA_SHIFT) + ((rgba)j << RED_SHIFT) ; + + for( i=0; i<256; i++ ) + freqtab[i] = 0 ; + for( y=0; y<height; y++ ) + for( x=0; x<width; x++ ) + if( ((((rgba)255 << ALPHA_SHIFT) + ((rgba)255 << RED_SHIFT)) + & pixels[y][x]) == sofar ) + freqtab[255 & (pixels[y][x] >> GREEN_SHIFT)] ++ ; + j = 0 ; + for( i=0; i<256; i++ ) { + if( freqtab[i] == 0 ) { + return sofar + ((rgba)i << GREEN_SHIFT); + } + if( freqtab[i] < freqtab[j] ) { + j = i ; + } + } + sofar += (rgba)j << GREEN_SHIFT ; + + for( i=0; i<256; i++ ) + freqtab[i] = 0 ; + for( y=0; y<height; y++ ) + for( x=0; x<width; x++ ) + if( ((((rgba)255 << ALPHA_SHIFT) + + ((rgba)255 << RED_SHIFT) + + ((rgba)255 << GREEN_SHIFT)) + & pixels[y][x]) == sofar ) + freqtab[255 & (pixels[y][x] >> BLUE_SHIFT)] ++ ; + for( i=0; i<256; i++ ) { + if( freqtab[i] == 0 ) { + return sofar + ((rgba)i << BLUE_SHIFT); + } + } + + return 0 ; +} + +int +main(int argc,char **argv) +{ + int option ; + const char *unzipper = NULL ; + const char *infile = NULL ; + + setlocale(LC_ALL,""); + progname = argv[0] ; + + if( argc <= 1 ) gpl_blurb() ; + + init_flatspec(&flatspec) ; + while( (option=getopt_long(argc,argv,"-"OPTSTRING,longopts,NULL)) >= 0 ) + switch(option) { + #define OPTION(char,long,desc,man) case char: + #include "options.i" + case 1: + if( infile ) + add_layer_request(&flatspec,optarg); + else + infile = optarg ; + break ; + case '?': + usage(stderr); + default: + FatalUnexpected("Getopt(_long) unexpectedly returned '%c'",option); + } + if( infile == NULL ) { + usage(stderr); + } + + read_or_mmap_xcf(infile,unzipper); + getBasicXcfInfo() ; + initColormap(); + + complete_flatspec(&flatspec,guessIndexed); + if( flatspec.process_in_memory ) { + rgba **allPixels = flattenAll(&flatspec); + + analyse_colormode(&flatspec,allPixels,guessIndexed); + + /* See if we can do alpha compaction. + */ + if( flatspec.partial_transparency_mode != ALLOW_PARTIAL_TRANSPARENCY && + !FULLALPHA(flatspec.default_pixel) && + flatspec.out_color_mode != COLOR_INDEXED ) { + rgba unused = findUnusedColor(allPixels, + flatspec.dim.width, + flatspec.dim.height); + if( unused && (flatspec.out_color_mode == COLOR_RGB || + degrayPixel(unused) >= 0) ) { + unsigned x,y ; + unused = NEWALPHA(unused,0) ; + for( y=0; y<flatspec.dim.height; y++) + for( x=0; x<flatspec.dim.width; x++) + if( allPixels[y][x] == 0 ) + allPixels[y][x] = unused ; + flatspec.default_pixel = unused ; + } + } + shipoutWithCallback(&flatspec,allPixels,selectCallback()); + } else { + flattenIncrementally(&flatspec,selectCallback()); + } + if( libpng ) { + png_write_end(libpng,libpng2); + png_destroy_write_struct(&libpng,&libpng2); + } + if( outfile ) { + closeout(outfile,flatspec.output_filename); + } + return 0 ; +} |