File indexing completed on 2025-03-09 04:09:52

0001 /* Flattening selections function for xcftools
0002  *
0003  * This file was written by Henning Makholm <henning@makholm.net>
0004  * It is hereby in the public domain.
0005  * 
0006  * In jurisdictions that do not recognise grants of copyright to the
0007  * public domain: I, the author and (presumably, in those jurisdictions)
0008  * copyright holder, hereby permit anyone to distribute and use this code,
0009  * in source code or binary form, with or without modifications. This
0010  * permission is world-wide and irrevocable.
0011  *
0012  * Of course, I will not be liable for any errors or shortcomings in the
0013  * code, since I give it away without asking any compenstations.
0014  *
0015  * If you use or distribute this code, I would appreciate receiving
0016  * credit for writing it, in whichever way you find proper and customary.
0017  */
0018 
0019 #include "xcftools.h"
0020 #include "flatten.h"
0021 #include <string.h>
0022 #include <stdlib.h>
0023 
0024 void
0025 init_flatspec(struct FlattenSpec *spec)
0026 {
0027   spec->window_mode = USE_CANVAS ;
0028   spec->default_pixel = PERHAPS_ALPHA_CHANNEL ;
0029   spec->numLayers = 0 ;
0030   spec->layers = NULL ;
0031   spec->transmap_filename = NULL ;
0032   spec->output_filename = "-" ;
0033   spec->out_color_mode = COLOR_BY_CONTENTS ;
0034   spec->partial_transparency_mode = ALLOW_PARTIAL_TRANSPARENCY ;
0035   spec->process_in_memory = 0 ;
0036   spec->gimpish_indexed = 1 ;
0037 }
0038 
0039 int
0040 add_layer_request(struct FlattenSpec *spec, const char *layer)
0041 {
0042   spec->layers = realloc(spec->layers,
0043                          sizeof(struct xcfLayer) * (1+spec->numLayers));
0044   if( spec->layers == NULL ) {
0045     FatalUnexpected(_("Out of memory"));
0046     return XCF_ERROR;
0047   }
0048   spec->layers[spec->numLayers].name = layer ;
0049   spec->layers[spec->numLayers].mode = (GimpLayerModeEffects)-1 ;
0050   spec->layers[spec->numLayers].opacity = 9999 ;
0051   spec->layers[spec->numLayers].hasMask = -1 ;
0052   spec->numLayers++ ;
0053   return XCF_OK;
0054 }
0055 
0056 struct xcfLayer *
0057 lastlayerspec(struct FlattenSpec *spec,const char *option)
0058 {
0059   if( spec->numLayers == 0 ) {
0060     FatalGeneric(20,_("The %s option must follow a layer name on the "
0061                       "command line"),option);
0062     return XCF_PTR_EMPTY;
0063   }
0064   return spec->layers + (spec->numLayers-1) ;
0065 }
0066 
0067 static int
0068 typeHasTransparency(GimpImageType type)
0069 {
0070   switch( type ) {
0071   case GIMP_RGB_IMAGE:
0072   case GIMP_GRAY_IMAGE:
0073   case GIMP_INDEXED_IMAGE:
0074     return 0 ;
0075   case GIMP_RGBA_IMAGE:
0076   case GIMP_GRAYA_IMAGE:
0077   case GIMP_INDEXEDA_IMAGE:
0078     return 1 ;
0079   }
0080   return 1 ;
0081 }
0082 
0083 static enum out_color_mode
0084 color_by_layers(struct FlattenSpec *spec)
0085 {
0086   int colormap_is_colored = 0 ;
0087   enum out_color_mode grayish ;
0088   int i ;
0089 
0090   if( spec->default_pixel == CHECKERED_BACKGROUND )
0091     grayish = COLOR_GRAY ;
0092   else {
0093     int degrayed = degrayPixel(spec->default_pixel);
0094     if( degrayed < 0 ) {
0095       return COLOR_RGB ;
0096     } else if( spec->gimpish_indexed &&
0097                (degrayed == 0 || degrayed == 255) ) {
0098       grayish = COLOR_MONO ;
0099     } else {
0100       grayish = COLOR_GRAY ;
0101     }
0102   }
0103   for( i=0; i<colormapLength; i++ ) {
0104     if( colormap[i] == NEWALPHA(0,0) || colormap[i] == NEWALPHA(-1,0) )
0105       continue ;
0106     if( degrayPixel(colormap[i]) == -1 ) {
0107       colormap_is_colored = 1 ;
0108       break ;
0109     } else {
0110       grayish = COLOR_GRAY ;
0111     }
0112   }
0113   for( i=0; i<spec->numLayers; i++ )
0114     switch( spec->layers[i].type ) {
0115     case GIMP_RGB_IMAGE:
0116     case GIMP_RGBA_IMAGE:
0117       return COLOR_RGB ;
0118     case GIMP_GRAY_IMAGE:
0119     case GIMP_GRAYA_IMAGE:
0120       grayish = COLOR_GRAY ;
0121       break ;
0122     case GIMP_INDEXED_IMAGE:
0123     case GIMP_INDEXEDA_IMAGE:
0124       if( colormap_is_colored ) return COLOR_RGB ;
0125       break ;
0126     }
0127   return grayish ;
0128 }
0129 
0130 int
0131 complete_flatspec(struct FlattenSpec *spec, guesser guess_callback)
0132 {
0133   unsigned i ;
0134   int anyPartial ;
0135 
0136   /* Find the layers to convert.
0137    */
0138   if( spec->numLayers == 0 ) {
0139     spec->layers = XCF.layers ;
0140     spec->numLayers = XCF.numLayers ;
0141   } else {
0142     for( i=0; i<spec->numLayers; i++ ) {
0143       GimpLayerModeEffects mode ;
0144       int opacity, hasMask ;
0145       unsigned j ;
0146 
0147       for( j=0; ; j++ ) {
0148         if( j == XCF.numLayers ) {
0149           FatalGeneric(22,_("The image has no layer called '%s'"),
0150                        spec->layers[i].name);
0151           return XCF_ERROR;
0152         }
0153         if( strcmp(spec->layers[i].name,XCF.layers[j].name) == 0 )
0154           break ;
0155       }
0156       mode = spec->layers[i].mode == (GimpLayerModeEffects)-1 ?
0157         XCF.layers[j].mode : spec->layers[i].mode ;
0158       opacity = spec->layers[i].opacity == 9999 ?
0159         XCF.layers[j].opacity : spec->layers[i].opacity ;
0160       hasMask = spec->layers[i].hasMask == -1 ?
0161         XCF.layers[j].hasMask : spec->layers[i].hasMask ;
0162       if( hasMask && !XCF.layers[j].hasMask &&
0163               XCF.layers[j].mask.hierarchy == 0 ) {
0164         FatalGeneric(22,_("Layer '%s' has no layer mask to enable"),
0165                      spec->layers[i].name);
0166         return XCF_ERROR;
0167       }
0168       spec->layers[i] = XCF.layers[j] ;
0169       spec->layers[i].mode = mode ;
0170       spec->layers[i].opacity = opacity ;
0171       spec->layers[i].hasMask = hasMask ;
0172       spec->layers[i].isVisible = 1 ;
0173     }
0174   }
0175 
0176   /* Force the mode of the lowest visible layer to be Normal or Dissolve.
0177    * That may not be logical, but the Gimp does it
0178    */
0179   for( i=0; i < spec->numLayers; i++ ) {
0180     if( spec->layers[i].isVisible ) {
0181       if( spec->layers[i].mode != GIMP_DISSOLVE_MODE )
0182         spec->layers[i].mode = GIMP_NORMAL_MODE ;
0183       break ;
0184     }
0185   }
0186 
0187   /* Mimic the Gimp's behavior on indexed layers */
0188   if( XCF.type == GIMP_INDEXED && spec->gimpish_indexed ) {
0189     for( i=0; i<spec->numLayers; i++ )
0190       if( spec->layers[i].mode != GIMP_DISSOLVE_MODE )
0191         spec->layers[i].mode = GIMP_NORMAL_NOPARTIAL_MODE ;
0192   } else
0193     spec->gimpish_indexed = 0 ;
0194 
0195   /* compute dimensions of the window */
0196   if( spec->window_mode == AUTOCROP ) {
0197     int first = 1 ;
0198     for( i=0; i<spec->numLayers; i++ )
0199       if( spec->layers[i].isVisible ) {
0200         if (computeDimensions(&spec->layers[i].dim) != XCF_OK) {
0201           return XCF_ERROR;
0202         }
0203         if( first ) {
0204           spec->dim = spec->layers[i].dim ;
0205           first = 0 ;
0206         } else {
0207           if( spec->dim.c.l > spec->layers[i].dim.c.l )
0208             spec->dim.c.l = spec->layers[i].dim.c.l ;
0209           if( spec->dim.c.r < spec->layers[i].dim.c.r )
0210             spec->dim.c.r = spec->layers[i].dim.c.r ;
0211           if( spec->dim.c.t > spec->layers[i].dim.c.t )
0212             spec->dim.c.t = spec->layers[i].dim.c.t ;
0213           if( spec->dim.c.b < spec->layers[i].dim.c.b )
0214             spec->dim.c.b = spec->layers[i].dim.c.b ;
0215         }
0216       }
0217     if( first ) {
0218       spec->window_mode = USE_CANVAS ;
0219     } else {
0220       spec->dim.width = spec->dim.c.r - spec->dim.c.l ;
0221       spec->dim.height = spec->dim.c.b - spec->dim.c.t ;
0222     }
0223   }
0224   if( spec->window_mode != AUTOCROP ) {
0225     if( (spec->window_mode & MANUAL_OFFSET) == 0 )
0226       spec->dim.c.t = spec->dim.c.l = 0 ;
0227     if( (spec->window_mode & MANUAL_CROP) == 0 ) {      
0228       spec->dim.height = XCF.height ;
0229       spec->dim.width = XCF.width ;
0230     }
0231   }
0232   if (computeDimensions(&spec->dim) != XCF_OK) {
0233     return XCF_ERROR;
0234   }
0235 
0236   /* Turn off layers that we don't hit at all */
0237   for( i=0; i<spec->numLayers; i++ )
0238     if( spec->layers[i].isVisible &&
0239         disjointRects(spec->dim.c,spec->layers[i].dim.c) )
0240       spec->layers[i].isVisible = 0 ;
0241   
0242   /* See if there is a completely covering layer somewhere in the stack */
0243   /* Also check if partial transparency is possible */
0244   anyPartial = 0 ;
0245   for( i=spec->numLayers; i-- ; ) {
0246     if( !spec->layers[i].isVisible )
0247       continue ;
0248     if( typeHasTransparency(spec->layers[i].type) ) {
0249       if( spec->layers[i].mode == GIMP_NORMAL_MODE )
0250         anyPartial = 1;
0251     } else if( isSubrect(spec->dim.c,spec->layers[i].dim.c) &&
0252                !spec->layers[i].hasMask &&
0253              (spec->layers[i].mode == GIMP_NORMAL_MODE ||
0254               spec->layers[i].mode == GIMP_NORMAL_NOPARTIAL_MODE ||
0255               spec->layers[i].mode == GIMP_DISSOLVE_MODE) ) {
0256       /* This layer fills out the entire image.
0257        * Turn off only lower layers, and note that we cannot have
0258        * transparency at all.
0259        */
0260       while(i) spec->layers[--i].isVisible = 0 ;
0261       if( spec->default_pixel != FORCE_ALPHA_CHANNEL )
0262         spec->default_pixel = NEWALPHA(colormap[0],255);
0263       anyPartial = 0 ;
0264       break ;
0265     }
0266   }
0267   if( spec->partial_transparency_mode == ALLOW_PARTIAL_TRANSPARENCY &&
0268       (!anyPartial || ALPHA(spec->default_pixel) >= 128) )
0269     spec->partial_transparency_mode = PARTIAL_TRANSPARENCY_IMPOSSIBLE ;
0270 
0271   /* Initialize layers and print overview if we're verbose */
0272   for( i=spec->numLayers; i--; )
0273     if( spec->layers[i].isVisible ) {
0274         if (initLayer(&spec->layers[i]) != XCF_OK) {
0275             return XCF_ERROR;
0276         }
0277       if( verboseFlag ) {
0278         fprintf(stderr,"%dx%d%+d%+d %s %s",
0279                 spec->layers[i].dim.width, spec->layers[i].dim.height,
0280                 spec->layers[i].dim.c.l - spec->dim.c.l,
0281                 spec->layers[i].dim.c.t - spec->dim.c.t,
0282                 _(showGimpImageType(spec->layers[i].type)),
0283                 _(showGimpLayerModeEffects(spec->layers[i].mode)));
0284         if( spec->layers[i].opacity < 255 )
0285           fprintf(stderr,"/%02d%%",spec->layers[i].opacity * 100 / 255);
0286         if( XCF.layers[i].hasMask )
0287           fprintf(stderr,_("/mask"));
0288         fprintf(stderr," %s\n",spec->layers[i].name);
0289       }
0290     }
0291   
0292   /* Resolve color mode unless we wait until we have the entire image */
0293   if( spec->out_color_mode == COLOR_BY_CONTENTS &&
0294       !spec->process_in_memory ) {
0295     if( guess_callback )
0296       spec->out_color_mode = guess_callback(spec,NULL);
0297     if( spec->out_color_mode == COLOR_BY_CONTENTS )
0298       spec->out_color_mode = color_by_layers(spec) ;
0299   }
0300   return XCF_OK;
0301 }
0302 
0303 int
0304 analyse_colormode(struct FlattenSpec *spec,rgba **allPixels,
0305                   guesser guess_callback)
0306 {
0307   unsigned x,y ;
0308   int status ;
0309   /* 8 - looking for any transparency
0310    * 4 - looking for partially transparent pixels
0311    * 2 - looking for pixels other than black and white
0312    * 1 - looking for colored pixels
0313    */
0314   int known_absent = 0 ;
0315   int assume_present = 0 ;
0316 
0317   if( spec->out_color_mode == COLOR_BY_CONTENTS && guess_callback )
0318     spec->out_color_mode = guess_callback(spec,allPixels) ;
0319 
0320   if( spec->out_color_mode == COLOR_RGB     ) assume_present |= 3 ;
0321   if( spec->out_color_mode == COLOR_INDEXED ) assume_present |= 3 ;
0322   if( spec->out_color_mode == COLOR_GRAY    ) assume_present |= 2 ;
0323   switch( color_by_layers(spec) ) {
0324   case COLOR_GRAY: known_absent |= 1 ; break ;
0325   case COLOR_MONO: known_absent |= 3 ; break ;
0326   default: break ;
0327   }
0328   if( spec->partial_transparency_mode == DISSOLVE_PARTIAL_TRANSPARENCY ||
0329       spec->partial_transparency_mode == PARTIAL_TRANSPARENCY_IMPOSSIBLE )
0330     known_absent |= 4 ;
0331   if( ALPHA(spec->default_pixel) >= 128 )               known_absent |= 12 ;
0332   else if( spec->default_pixel == FORCE_ALPHA_CHANNEL ) assume_present |= 8 ;
0333 
0334   status = 15 - (known_absent | assume_present) ;
0335   
0336   for( y=0; status && y<spec->dim.height; y++ ) {
0337     rgba *row = allPixels[y] ;
0338     if( (status & 3) != 0 ) {
0339       /* We're still interested in color */
0340       for( x=0; status && x<spec->dim.width; x++ ) {
0341         if( NULLALPHA(row[x]) )
0342           status &= ~8 ;
0343         else {
0344           rgba full = row[x] | (255 << ALPHA_SHIFT) ;
0345           if( !FULLALPHA(row[x]) ) status &= ~12 ;
0346           if( full == NEWALPHA(0,255) || full == NEWALPHA(-1,255) )
0347             /* Black or white */ ;
0348           else if( degrayPixel(row[x]) != -1 )
0349             status &= ~2 ; /* gray */
0350           else
0351             status &= ~3 ; /* color */
0352         }
0353       }
0354     } else {
0355       /* Not interested in color */
0356       for( x=0; status && x<spec->dim.width; x++ ) {
0357         if( NULLALPHA(row[x]) )
0358           status &= ~8 ;
0359         else if( !FULLALPHA(row[x]) )
0360           status &= ~12 ;
0361       }
0362     }
0363   }
0364 
0365   status |= known_absent ;
0366   
0367   switch( spec->out_color_mode ) {
0368   case COLOR_INDEXED: /* The caller takes responsibility */
0369   case COLOR_RGB: /* Everything is fine. */
0370     break ;
0371   case COLOR_GRAY:
0372     if( (status & 1) == 0 ) {
0373       FatalGeneric(103,
0374                    _("Grayscale output selected, but colored pixel(s) found"));
0375       return XCF_ERROR;
0376     }
0377     break ;
0378   case COLOR_MONO:
0379     if( (status & 2) == 0 ) {
0380       FatalGeneric(103,_("Monochrome output selected, but not all pixels "
0381                          "are black or white"));
0382       return XCF_ERROR;
0383     }
0384     break ;
0385   case COLOR_BY_FILENAME: /* Should not happen ... */
0386   case COLOR_BY_CONTENTS:
0387     if( (status & 1) == 0 )
0388       spec->out_color_mode = COLOR_RGB ;
0389     else if( (status & 2) == 0 )
0390       spec->out_color_mode = COLOR_GRAY ;
0391     else
0392       spec->out_color_mode = COLOR_MONO ;
0393     break ;
0394   }
0395 
0396   if( (status & 12) == 12 ) /* No transparency found */
0397     spec->default_pixel = NEWALPHA(colormap[0],255);
0398   else if( (status & 12) == 4 )
0399     spec->partial_transparency_mode = PARTIAL_TRANSPARENCY_IMPOSSIBLE ;
0400   return XCF_OK;
0401 }