#include "oily_png_ext.h" ///// Pixel encoding functions ////////////////////////////////////////// void oily_png_encode_pixel_grayscale(PIXEL pixel, BYTE* bytes, long pos, VALUE palette) { // Assume R == G == B. ChunkyPNG uses the B byte fot performance reasons. // We'll uses the same to reomain compatible with ChunkyPNG. bytes[pos] = B_BYTE(pixel); } void oily_png_encode_pixel_grayscale_alpha(PIXEL pixel, BYTE* bytes, long pos, VALUE palette) { // Assume R == G == B. ChunkyPNG uses the B byte fot performance reasons. // We'll uses the same to reomain compatible with ChunkyPNG. bytes[pos + 0] = B_BYTE(pixel); bytes[pos + 1] = A_BYTE(pixel); } void oily_png_encode_pixel_truecolor(PIXEL pixel, BYTE* bytes, long pos, VALUE palette) { bytes[pos + 0] = R_BYTE(pixel); bytes[pos + 1] = G_BYTE(pixel); bytes[pos + 2] = B_BYTE(pixel); } void oily_png_encode_pixel_truecolor_alpha(PIXEL pixel, BYTE* bytes, long pos, VALUE palette) { bytes[pos + 0] = R_BYTE(pixel); bytes[pos + 1] = G_BYTE(pixel); bytes[pos + 2] = B_BYTE(pixel); bytes[pos + 3] = A_BYTE(pixel); } void oily_png_encode_pixel_indexed(PIXEL pixel, BYTE* bytes, long pos, VALUE palette) { bytes[pos] = (BYTE) NUM2UINT(rb_funcall(palette, rb_intern("index"), 1, UINT2NUM(pixel))); } ///// Scanline filtering functions ////////////////////////////////////////// void oily_png_encode_filter_sub(BYTE* bytes, long pos, long line_size, char pixel_size) { long x; for (x = line_size - 1; x > pixel_size; x--) { FILTER_BYTE(bytes[pos + x], bytes[pos + x - pixel_size]); } } void oily_png_encode_filter_up(BYTE* bytes, long pos, long line_size, char pixel_size) { long x; if (pos >= line_size) { for (x = line_size - 1; x > 0; x--) { FILTER_BYTE(bytes[pos + x], bytes[pos + x - line_size]); } } } void oily_png_encode_filter_average(BYTE* bytes, long pos, long line_size, char pixel_size) { long x; BYTE a, b; for (x = line_size - 1; x > 0; x--) { a = (x > pixel_size) ? bytes[pos + x - pixel_size] : 0; b = (pos >= line_size) ? bytes[pos + x - line_size] : 0; FILTER_BYTE(bytes[pos + x], (a + b) >> 1); } } void oily_png_encode_filter_paeth(BYTE* bytes, long pos, long line_size, char pixel_size) { long x; int p, pa, pb, pc; BYTE a, b, c, pr; for (x = line_size - 1; x > 0; x--) { a = (x > pixel_size) ? bytes[pos + x - pixel_size] : 0; b = (pos >= line_size) ? bytes[pos + x - line_size] : 0; c = (pos >= line_size && x > pixel_size) ? bytes[pos + x - line_size - pixel_size] : 0; p = a + b - c; pa = abs(p - a); pb = abs(p - b); pc = abs(p - c); pr = (pa <= pb && pa <= pc) ? a : (pb <= pc ? b : c); FILTER_BYTE(bytes[pos + x], pr); } } VALUE oily_png_encode_png_image_pass_to_stream(VALUE self, VALUE stream, VALUE color_mode, VALUE filtering) { // Get the data char depth = 8; long width = FIX2LONG(rb_funcall(self, rb_intern("width"), 0)); long height = FIX2LONG(rb_funcall(self, rb_intern("height"), 0)); VALUE pixels = rb_funcall(self, rb_intern("pixels"), 0); if (RARRAY_LEN(pixels) != width * height) { rb_raise(rb_eRuntimeError, "The number of pixels does not match the canvas dimensions."); } // Get the encoding palette if we're encoding to an indexed bytestream. VALUE palette = Qnil; if (FIX2INT(color_mode) == OILY_PNG_COLOR_INDEXED) { palette = rb_funcall(self, rb_intern("encoding_palette"), 0); } char pixel_size = oily_png_pixel_bytesize(FIX2INT(color_mode), depth); long line_size = oily_png_scanline_bytesize(FIX2INT(color_mode), depth, width); long pass_size = oily_png_pass_bytesize(FIX2INT(color_mode), depth, width, height); // Allocate memory for the byte array. BYTE* bytes = ALLOCA_N(BYTE, pass_size); // Select out pixel encoder function based on the color mode. void (*pixel_encoder)(PIXEL, BYTE*, long, VALUE) = NULL; switch (FIX2INT(color_mode)) { case OILY_PNG_COLOR_GRAYSCALE: pixel_encoder = &oily_png_encode_pixel_grayscale; break; case OILY_PNG_COLOR_TRUECOLOR: pixel_encoder = &oily_png_encode_pixel_truecolor; break; case OILY_PNG_COLOR_INDEXED: pixel_encoder = &oily_png_encode_pixel_indexed; break; case OILY_PNG_COLOR_GRAYSCALE_ALPHA: pixel_encoder = &oily_png_encode_pixel_grayscale_alpha; break; case OILY_PNG_COLOR_TRUECOLOR_ALPHA: pixel_encoder = &oily_png_encode_pixel_truecolor_alpha; break; default: rb_raise(rb_eRuntimeError, "Unsupported color mode: %d", FIX2INT(color_mode)); } // Loop over all the pixels to encode them into the byte array. PIXEL pixel; long x, y, pos; for (y = 0; y < height; y++) { bytes[line_size * y] = (BYTE) FIX2INT(filtering); for (x = 0; x < width; x++) { pixel = NUM2UINT(rb_ary_entry(pixels, y * width + x)); pos = (line_size * y) + (pixel_size * x) + 1; pixel_encoder(pixel, bytes, pos, palette); } } // Check if we are going to apply any filtering if (FIX2INT(filtering) != OILY_PNG_FILTER_NONE) { // Assign the chosen filter function to the scanline_filter variable. void (*scanline_filter)(BYTE*, long, long, char) = NULL; switch (FIX2INT(filtering)) { case OILY_PNG_FILTER_SUB: scanline_filter = &oily_png_encode_filter_sub; break; case OILY_PNG_FILTER_UP: scanline_filter = &oily_png_encode_filter_up; break; case OILY_PNG_FILTER_AVERAGE: scanline_filter = &oily_png_encode_filter_average; break; case OILY_PNG_FILTER_PAETH: scanline_filter = &oily_png_encode_filter_paeth; break; default: rb_raise(rb_eRuntimeError, "Unsupported filter type: %d", FIX2INT(filtering)); } // Now, apply the scanline_filter function to every line, backwards. for (y = height - 1; y >= 0; y--) { scanline_filter(bytes, line_size * y, line_size, pixel_size); } } // Append to encoded image pass to the output stream. rb_str_cat(stream, (char*) bytes, pass_size); return Qnil; }