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 /flatten.c | |
download | xcftools-7b7cd6da61b1fcc0f2a3ecce2cb9e6c42782c717.zip xcftools-7b7cd6da61b1fcc0f2a3ecce2cb9e6c42782c717.tar.gz xcftools-7b7cd6da61b1fcc0f2a3ecce2cb9e6c42782c717.tar.bz2 |
Import of release 0.7
Diffstat (limited to 'flatten.c')
-rw-r--r-- | flatten.c | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/flatten.c b/flatten.c new file mode 100644 index 0000000..9b148bb --- /dev/null +++ b/flatten.c @@ -0,0 +1,557 @@ +/* Flattning functions for xcftools + * + * 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 "pixels.h" +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +static rgba __ATTRIBUTE__((noinline,const)) +composite_one(rgba bot,rgba top) +{ + unsigned tfrac, alpha ; + + tfrac = ALPHA(top) ; + alpha = 255 ; + if( !FULLALPHA(bot) ) { + alpha = 255 ^ scaletable[255-ALPHA(bot)][255-ALPHA(top)] ; + /* This peculiar combination of ^ and - makes the GCC code + * generator for i386 particularly happy. + */ + tfrac = (256*ALPHA(top) - 1) / alpha ; + /* Tfrac is the fraction of the coposited pixel's covered area + * that comes from the top pixel. + * For mathematical accuracy we ought to scale by 255 and + * subtract alpha/2, but this is faster, and never misses the + * true value by more than one 1/255. This effect is completely + * overshadowed by the linear interpolation in the first place. + * (I.e. gamma is ignored when combining intensities). + * [In any case, complete fairness is not possible: if the + * bottom pixel had alpha=170 and the top has alpha=102, + * each should contribute equally to the color of the + * resulting alpha=204 pixel, which is not possible in general] + * Subtracting one helps the topfrac never be 256, which would + * be bad. + * On the other hand it means that we would get tfrac=-1 if the + * top pixel is completely transparent, and we get a division + * by zero if _both_ pixels are fully transparent. These cases + * must be handled by all callers. + */ + } + return (alpha << ALPHA_SHIFT) + + ((uint32_t)scaletable[ tfrac ][255&(top>>RED_SHIFT )] << RED_SHIFT ) + + ((uint32_t)scaletable[ tfrac ][255&(top>>GREEN_SHIFT)] << GREEN_SHIFT ) + + ((uint32_t)scaletable[ tfrac ][255&(top>>BLUE_SHIFT )] << BLUE_SHIFT ) + + ((uint32_t)scaletable[255^tfrac][255&(bot>>RED_SHIFT )] << RED_SHIFT ) + + ((uint32_t)scaletable[255^tfrac][255&(bot>>GREEN_SHIFT)] << GREEN_SHIFT ) + + ((uint32_t)scaletable[255^tfrac][255&(bot>>BLUE_SHIFT )] << BLUE_SHIFT ) + ; +} + +/* merge_normal() takes ownership of bot. + * merge_normal() will share ownership of top. + * Return: may be shared. + */ +static struct Tile * __ATTRIBUTE__((noinline)) +merge_normal(struct Tile *bot, struct Tile *top) +{ + unsigned i ; + assertTileCompatibility(bot,top); + + /* See if there is an easy winner */ + if( (bot->summary & TILESUMMARY_ALLNULL) || + (top->summary & TILESUMMARY_ALLFULL) ) { + freeTile(bot); + return top ; + } + if( top->summary & TILESUMMARY_ALLNULL ) { + freeTile(top); + return bot ; + } + + /* Try hard to make top win */ + for( i=0; ; i++ ) { + if( i == top->count ) { + freeTile(bot); + return top ; + } + if( !(NULLALPHA(bot->pixels[i]) || FULLALPHA(top->pixels[i])) ) + break ; + } + + /* Otherwise bot wins, but is forever changed ... */ + if( (top->summary & TILESUMMARY_ALLNULL) == 0 ) { + unsigned i ; + invalidateSummary(bot,0); + for( i=0 ; i < top->count ; i++ ) { + if( !NULLALPHA(top->pixels[i]) ) { + if( FULLALPHA(top->pixels[i]) || NULLALPHA(bot->pixels[i]) ) + bot->pixels[i] = top->pixels[i] ; + else + bot->pixels[i] = composite_one(bot->pixels[i],top->pixels[i]); + } + } + } + freeTile(top); + return bot ; +} + +#define exotic_combinator static inline unsigned __ATTRIBUTE__((const)) + + + +exotic_combinator +ucombine_ADDITION(uint8_t bot,uint8_t top) +{ + return bot+top > 255 ? 255 : bot+top ; +} + +exotic_combinator +ucombine_SUBTRACT(uint8_t bot,uint8_t top) +{ + return top>bot ? 0 : bot-top ; +} + +exotic_combinator +ucombine_LIGHTEN_ONLY(uint8_t bot,uint8_t top) +{ + return top > bot ? top : bot ; +} + +exotic_combinator +ucombine_DARKEN_ONLY(uint8_t bot,uint8_t top) +{ + return top < bot ? top : bot ; +} + +exotic_combinator +ucombine_DIFFERENCE(uint8_t bot,uint8_t top) +{ + return top > bot ? top-bot : bot-top ; +} + +exotic_combinator +ucombine_MULTIPLY(uint8_t bot,uint8_t top) +{ + return scaletable[bot][top] ; +} + +exotic_combinator +ucombine_DIVIDE(uint8_t bot,uint8_t top) +{ + int result = (int)bot*256 / (1+top) ; + return result >= 256 ? 255 : result ; +} + +exotic_combinator +ucombine_SCREEN(uint8_t bot,uint8_t top) +{ + /* An inverted version of "multiply" */ + return 255 ^ scaletable[255-bot][255-top] ; +} + +exotic_combinator +ucombine_OVERLAY(uint8_t bot,uint8_t top) +{ + return scaletable[bot][bot] + + 2*scaletable[top][scaletable[bot][255-bot]] ; + /* This strange formula is equivalent to + * (1-top)*(bot^2) + top*(1-(1-top)^2) + * that is, the top value is used to interpolate between + * the self-multiply and the self-screen of the bottom. + */ + /* Note: This is exactly what the "Soft light" effect also + * does, though with different code in the Gimp. + */ +} + +exotic_combinator +ucombine_DODGE(uint8_t bot,uint8_t top) +{ + return ucombine_DIVIDE(bot,255-top); +} + +exotic_combinator +ucombine_BURN(uint8_t bot,uint8_t top) +{ + return 255 - ucombine_DIVIDE(255-bot,top); +} + +exotic_combinator +ucombine_HARDLIGHT(uint8_t bot,uint8_t top) +{ + if( top >= 128 ) + return 255 ^ scaletable[255-bot][2*(255-top)] ; + else + return scaletable[bot][2*top]; + /* The code that implements "hardlight" in Gimp 2.2.10 has some + * rounding errors, but this is undoubtedly what is meant. + */ +} + +exotic_combinator +ucombine_GRAIN_EXTRACT(uint8_t bot,uint8_t top) +{ + int temp = (int)bot - (int)top + 128 ; + return temp < 0 ? 0 : temp >= 256 ? 255 : temp ; +} + +exotic_combinator +ucombine_GRAIN_MERGE(uint8_t bot,uint8_t top) +{ + int temp = (int)bot + (int)top - 128 ; + return temp < 0 ? 0 : temp >= 256 ? 255 : temp ; +} + + + +/* merge_exotic() destructively updates bot. + * merge_exotoc() reads but does not free top. + */ +static void __ATTRIBUTE__((noinline)) +merge_exotic(struct Tile *bot, const struct Tile *top, + GimpLayerModeEffects mode) +{ + unsigned i ; + assertTileCompatibility(bot,top); + if( (bot->summary & TILESUMMARY_ALLNULL) != 0 ) return ; + if( (top->summary & TILESUMMARY_ALLNULL) != 0 ) return ; + assert( bot->refcount == 1 ); + /* The transparency status of bot never changes */ + + for( i=0; i < top->count ; i++ ) { + uint32_t red, green, blue ; + if( NULLALPHA(bot->pixels[i]) || NULLALPHA(top->pixels[i]) ) + continue ; +#define UNIFORM(mode) case GIMP_ ## mode ## _MODE: \ + red = ucombine_ ## mode (bot->pixels[i]>>RED_SHIFT , \ + top->pixels[i]>>RED_SHIFT ); \ + green = ucombine_ ## mode (bot->pixels[i]>>GREEN_SHIFT, \ + top->pixels[i]>>GREEN_SHIFT); \ + blue = ucombine_ ## mode (bot->pixels[i]>>BLUE_SHIFT , \ + top->pixels[i]>>BLUE_SHIFT ); \ + break ; + switch( mode ) { + case GIMP_NORMAL_MODE: + case GIMP_DISSOLVE_MODE: + FatalUnexpected("Normal and Dissolve mode can't happen here!"); + UNIFORM(ADDITION); + UNIFORM(SUBTRACT); + UNIFORM(LIGHTEN_ONLY); + UNIFORM(DARKEN_ONLY); + UNIFORM(DIFFERENCE); + UNIFORM(MULTIPLY); + UNIFORM(DIVIDE); + UNIFORM(SCREEN); + case GIMP_SOFTLIGHT_MODE: /* A synonym for "overlay"! */ + UNIFORM(OVERLAY); + UNIFORM(DODGE); + UNIFORM(BURN); + UNIFORM(HARDLIGHT); + UNIFORM(GRAIN_EXTRACT); + UNIFORM(GRAIN_MERGE); + default: + FatalUnsupportedXCF(_("'%s' layer mode"),showGimpLayerModeEffects(mode)); + } + if( FULLALPHA(bot->pixels[i] & top->pixels[i]) ) + bot->pixels[i] = (bot->pixels[i] & (255 << ALPHA_SHIFT)) + + (red << RED_SHIFT) + + (green << GREEN_SHIFT) + + (blue << BLUE_SHIFT) ; + else { + rgba bp = bot->pixels[i] ; + /* In a sane world, the alpha of the top pixel would simply be + * used to interpolate linearly between the bottom pixel's base + * color and the effect-computed color. + * But no! What the Gimp actually does is empirically + * described by the following (which borrows code from + * composite_one() that makes no theoretical sense here): + */ + unsigned tfrac = ALPHA(top->pixels[i]) ; + if( !FULLALPHA(bp) ) { + unsigned pseudotop = (tfrac < ALPHA(bp) ? tfrac : ALPHA(bp)); + unsigned alpha = 255 ^ scaletable[255-ALPHA(bp)][255-pseudotop] ; + tfrac = (256*pseudotop - 1) / alpha ; + } + bot->pixels[i] = (bp & (255 << ALPHA_SHIFT)) + + ((rgba)scaletable[ tfrac ][ red ] << RED_SHIFT ) + + ((rgba)scaletable[ tfrac ][ green ] << GREEN_SHIFT) + + ((rgba)scaletable[ tfrac ][ blue ] << BLUE_SHIFT ) + + ((rgba)scaletable[255^tfrac][255&(bp>>RED_SHIFT )] << RED_SHIFT ) + + ((rgba)scaletable[255^tfrac][255&(bp>>GREEN_SHIFT)] << GREEN_SHIFT) + + ((rgba)scaletable[255^tfrac][255&(bp>>BLUE_SHIFT )] << BLUE_SHIFT ) ; + } + } + return ; +} + +static void +dissolveTile(struct Tile *tile) +{ + unsigned i ; + summary_t summary ; + assert( tile->refcount == 1 ); + if( (tile->summary & TILESUMMARY_CRISP) ) + return ; + summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLNULL + + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; + for( i = 0 ; i < tile->count ; i++ ) { + if( FULLALPHA(tile->pixels[i]) ) + summary &= ~TILESUMMARY_ALLNULL ; + else if ( NULLALPHA(tile->pixels[i]) ) + summary &= ~TILESUMMARY_ALLFULL ; + else if( ALPHA(tile->pixels[i]) > rand() % 0xFF ) { + tile->pixels[i] |= 255 << ALPHA_SHIFT ; + summary &= ~TILESUMMARY_ALLNULL ; + } else { + tile->pixels[i] = 0 ; + summary &= ~TILESUMMARY_ALLFULL ; + } + } + tile->summary = summary ; +} + +static void +roundAlpha(struct Tile *tile) +{ + unsigned i ; + summary_t summary ; + assert( tile->refcount == 1 ); + if( (tile->summary & TILESUMMARY_CRISP) ) + return ; + summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLNULL + + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; + for( i = 0 ; i < tile->count ; i++ ) { + if( ALPHA(tile->pixels[i]) >= 128 ) { + tile->pixels[i] |= 255 << ALPHA_SHIFT ; + summary &= ~TILESUMMARY_ALLNULL ; + } else { + tile->pixels[i] = 0 ; + summary &= ~TILESUMMARY_ALLFULL ; + } + } + tile->summary = summary ; +} + +/* flattenTopdown() shares ownership of top. + * The return value may be a shared tile. + */ +static struct Tile * +flattenTopdown(struct FlattenSpec *spec, struct Tile *top, + unsigned nlayers, const struct rect *where) +{ + struct Tile *tile; + + while( nlayers-- ) { + if( tileSummary(top) & TILESUMMARY_ALLFULL ) + return top ; + if( !spec->layers[nlayers].isVisible ) + continue ; + + tile = getLayerTile(&spec->layers[nlayers],where); + + if( tile->summary & TILESUMMARY_ALLNULL ) + continue ; /* Simulate a tail call */ + + switch( spec->layers[nlayers].mode ) { + case GIMP_NORMAL_NOPARTIAL_MODE: + roundAlpha(tile) ; + /* fall through */ + if(0) { + case GIMP_DISSOLVE_MODE: + dissolveTile(tile); + /* fall through */ + } + case GIMP_NORMAL_MODE: + top = merge_normal(tile,top); + break ; + default: + { + struct Tile *below, *above ; + unsigned i ; + if( !(top->summary & TILESUMMARY_ALLNULL) ) { + rgba tile_or = 0 ; + invalidateSummary(tile,0); + for( i=0; i<top->count; i++ ) + if( FULLALPHA(top->pixels[i]) ) + tile->pixels[i] = 0 ; + else + tile_or |= tile->pixels[i] ; + /* If the tile only has pixels that will be covered by 'top' anyway, + * forget it anyway. + */ + if( ALPHA(tile_or) == 0 ) { + freeTile(tile); + break ; /* from the switch, which will continue the while */ + } + } + /* Create a dummy top for the layers below this */ + if( top->summary & TILESUMMARY_CRISP ) { + above = forkTile(top); + } else { + summary_t summary = TILESUMMARY_ALLNULL ; + above = newTile(*where); + for( i=0; i<top->count; i++ ) + if( FULLALPHA(top->pixels[i]) ) { + above->pixels[i] = -1 ; + summary = 0 ; + } else + above->pixels[i] = 0 ; + above->summary = TILESUMMARY_UPTODATE + TILESUMMARY_CRISP + summary; + } + below = flattenTopdown(spec, above, nlayers, where); + if( below->refcount > 1 ) { + assert( below == top ); + /* This can only happen if 'below' is a copy of 'top' + * THROUGH 'above', which in turn means that none of all + * this is visible after all. So just free it and return 'top'. + */ + freeTile(below); + return top ; + } + merge_exotic(below,tile,spec->layers[nlayers].mode); + freeTile(tile); + top = merge_normal(below,top); + return top ; + } + } + } + return top ; +} + +static void +addBackground(struct FlattenSpec *spec, struct Tile *tile) +{ + unsigned i ; + + if( tileSummary(tile) & TILESUMMARY_ALLFULL ) + return ; + + switch( spec->partial_transparency_mode ) { + case FORBID_PARTIAL_TRANSPARENCY: + if( !(tileSummary(tile) & TILESUMMARY_CRISP) ) + FatalGeneric(102,_("Flattened image has partially transparent pixels")); + break ; + case DISSOLVE_PARTIAL_TRANSPARENCY: + dissolveTile(tile); + break ; + case ALLOW_PARTIAL_TRANSPARENCY: + case PARTIAL_TRANSPARENCY_IMPOSSIBLE: + break ; + } + + if( !FULLALPHA(spec->default_pixel) ) return ; + if( tileSummary(tile) & TILESUMMARY_ALLNULL ) { + fillTile(tile,spec->default_pixel); + } else { + for( i=0; i<tile->count; i++ ) + if( NULLALPHA(tile->pixels[i]) ) + tile->pixels[i] = spec->default_pixel ; + else if( FULLALPHA(tile->pixels[i]) ) + ; + else + tile->pixels[i] = composite_one(spec->default_pixel,tile->pixels[i]); + + tile->summary = TILESUMMARY_UPTODATE + + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; + } +} + +void +flattenIncrementally(struct FlattenSpec *spec,lineCallback callback) +{ + rgba *rows[TILE_HEIGHT] ; + unsigned i, y, nrows, ncols ; + struct rect where ; + struct Tile *tile ; + static struct Tile toptile ; + + toptile.count = TILE_HEIGHT * TILE_WIDTH ; + fillTile(&toptile,0); + + for( where.t = spec->dim.c.t; where.t < spec->dim.c.b; where.t=where.b ) { + where.b = (where.t+TILE_HEIGHT) - where.t % TILE_HEIGHT ; + if( where.b > spec->dim.c.b ) where.b = spec->dim.c.b ; + nrows = where.b - where.t ; + for( y = 0; y < nrows ; y++ ) + rows[y] = xcfmalloc(4*(spec->dim.c.r-spec->dim.c.l)); + + for( where.l = spec->dim.c.l; where.l < spec->dim.c.r; where.l=where.r ) { + where.r = (where.l+TILE_WIDTH) - where.l % TILE_WIDTH ; + if( where.r > spec->dim.c.r ) where.r = spec->dim.c.r ; + ncols = where.r - where.l ; + + toptile.count = ncols * nrows ; + toptile.refcount = 2 ; /* For bug checking */ + assert( toptile.summary == TILESUMMARY_UPTODATE + + TILESUMMARY_ALLNULL + TILESUMMARY_CRISP ); + tile = flattenTopdown(spec,&toptile,spec->numLayers,&where) ; + toptile.refcount-- ; /* addBackground may change destructively */ + addBackground(spec,tile); + + for( i = 0 ; i < tile->count ; i++ ) + if( NULLALPHA(tile->pixels[i]) ) + tile->pixels[i] = 0 ; + for( y = 0 ; y < nrows ; y++ ) + memcpy(rows[y] + (where.l - spec->dim.c.l), + tile->pixels + y * ncols, ncols*4); + + if( tile == &toptile ) { + fillTile(&toptile,0); + } else { + freeTile(tile); + } + } + for( y = 0 ; y < nrows ; y++ ) + callback(spec->dim.width,rows[y]); + } +} + +static rgba **collectPointer ; + +static void +collector(unsigned num,rgba *row) +{ + *collectPointer++ = row ; +} + +rgba ** +flattenAll(struct FlattenSpec *spec) +{ + rgba **rows = xcfmalloc(spec->dim.height * sizeof(rgba*)); + if( verboseFlag ) + fprintf(stderr,_("Flattening image ...")); + collectPointer = rows ; + flattenIncrementally(spec,collector); + if( verboseFlag ) + fprintf(stderr,"\n"); + return rows ; +} + +void +shipoutWithCallback(struct FlattenSpec *spec, rgba **pixels, + lineCallback callback) +{ + unsigned i ; + for( i = 0; i < spec->dim.height; i++ ) { + callback(spec->dim.width,pixels[i]); + } + xcffree(pixels); +} |