/* Stack layers of PNG files on top of each other. ./stack_png input.png... > output.png */ #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #endif #include"png_cache.h" /* Clamp an integer value to byte ranges. */ static int Clamp(int x) { return x >= 0 ? x <= 255 ? x : 255 : 0; } /* See "Recommendation ITU-R BT.709-6" https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf Search for "Overall opto-electronic transfer characteristics at source". But instead of 4.500 for the linear portion, we compute the coefficient to ramp up luminance without discontinuities. That number comes out to something like 4.514. pm_gamma709 from NetPBM's pm_gamma.h also took a similar approach. */ static float NonLinearLuminanceConversion(float input) { return 1.099 * pow(input, 0.45) - 0.099; } static float LuminanceToSignal(float input) { static const float cutoff = 0.018; return input >= cutoff ? NonLinearLuminanceConversion(input) : input * NonLinearLuminanceConversion(cutoff) / cutoff; } static float SignalToLuminance(float input) { static const float cutoff = 0.018; return input >= NonLinearLuminanceConversion(cutoff) ? pow((input + 0.099) / 1.099, 1 / 0.45) : input * cutoff / NonLinearLuminanceConversion(cutoff); } /* Compose a single color component. From pamcomp.c: output_transparency = underlay_transparency * overlay_transparency output_opacity = 1.0 - output_transparency if output_opacity == 0 then pick whatever color else output_color = (underlay_color * underlay_opacity * overlay_transparency + overlay_color * overlay_opacity) / output_opacity */ static int ComposeColor(int underlay_color, int underlay_alpha, int overlay_color, int overlay_alpha) { float color_a, opacity_a; float color_b, opacity_b; float color_output, opacity_output; /* These alpha values are handled by calling function. */ assert(overlay_alpha != 0); assert(overlay_alpha != 255); opacity_a = underlay_alpha / 255.0; opacity_b = overlay_alpha / 255.0; opacity_output = 1.0 - (1.0 - opacity_a) * (1.0 - opacity_b); if( opacity_output <= 0 ) return underlay_color; color_a = SignalToLuminance(underlay_color / 255.0); color_b = SignalToLuminance(overlay_color / 255.0); color_output = (color_a * opacity_a * (1.0 - opacity_b) + color_b * opacity_b) / opacity_output; return Clamp((int)(255.0 * LuminanceToSignal(color_output))); } /* Apply overlay pixels to underlay. */ static void ComposeImage(int pixel_count, png_bytep image_pixels, png_bytep overlay_pixels) { png_bytep r = overlay_pixels, w = image_pixels; int c; for(; pixel_count > 0; pixel_count--, r += 4, w += 4) { /* Leave original pixel untouched if overlay is transparent. */ if( r[3] == 0 ) continue; /* Overwrite original pixel if overlay is opaque. */ if( r[3] == 255 ) { memcpy(w, r, 4); continue; } /* Need to mix two pixels. */ for(c = 0; c < 3; c++) w[c] = ComposeColor(w[c], w[3], r[c], r[3]); /* Same as the opacity calculation in ComposeColor, but with integers. */ w[3] = ((255 * 255) - (255 - r[3]) * (255 - w[3])) / 255; } } /* Program entry. */ int main(int argc, char **argv) { ImageCache *cache = NULL, *base, *overlay; int i, pixel_count; if( argc < 2 ) { return printf("%s [...] > output.png\n", *argv); } if( isatty(STDOUT_FILENO) ) { fputs("Output should be redirected to a PNG file.\n", stderr); return 1; } #ifdef _WIN32 setmode(STDIN_FILENO, O_BINARY); setmode(STDOUT_FILENO, O_BINARY); #endif /* Load underlay image. */ base = LoadImage(argv[1], &cache); if( base == NULL ) return 1; pixel_count = (int)(base->image.width * base->image.height); /* Apply overlays. */ for(i = 2; i < argc; i++) { overlay = LoadImage(argv[i], &cache); if( overlay == NULL ) { FreeImageCache(&cache); return 1; } if( overlay->image.width != base->image.width || overlay->image.height != base->image.height ) { fprintf(stderr, "%s: overlay dimension mismatched, " "expect (%u,%u), got (%u,%u)\n", argv[i], base->image.width, base->image.height, overlay->image.width, overlay->image.height); FreeImageCache(&cache); return 1; } ComposeImage(pixel_count, base->pixels, overlay->pixels); } /* Write output. */ if( !png_image_write_to_stdio( &(base->image), stdout, 0, base->pixels, 0, NULL) ) { fputs("Error writing output\n", stderr); FreeImageCache(&cache); return 1; } FreeImageCache(&cache); return 0; }