oily_png-1.2.1/ 0000775 0000000 0000000 00000000000 12766025717 0013350 5 ustar 00root root 0000000 0000000 oily_png-1.2.1/.gitignore 0000664 0000000 0000000 00000000164 12766025717 0015341 0 ustar 00root root 0000000 0000000 Makefile
/conftest.dSYM
*.bundle
*.o
*.rbc
/.bundle/
Gemfile.lock
/pkg
oily_png-*.gem
.DS_Store
/spec/resources/_*.* oily_png-1.2.1/.travis.yml 0000664 0000000 0000000 00000000255 12766025717 0015463 0 ustar 00root root 0000000 0000000 sudo: false
language: ruby
script: bundle exec rake
rvm:
- "2.0"
- "2.1"
- "2.2"
- ruby-head
- rbx-19mode
matrix:
allow_failures:
- rvm: rbx-19mode
- rvm: ruby-head
oily_png-1.2.1/Gemfile 0000664 0000000 0000000 00000000151 12766025717 0014640 0 ustar 00root root 0000000 0000000 source 'https://rubygems.org'
gemspec
gem 'chunky_png', :path => ENV['CHUNKY_PNG'] if ENV['CHUNKY_PNG']
oily_png-1.2.1/LICENSE 0000664 0000000 0000000 00000002052 12766025717 0014354 0 ustar 00root root 0000000 0000000 Copyright (c) 2010-2014 Willem van Bergen
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
oily_png-1.2.1/README.rdoc 0000664 0000000 0000000 00000002157 12766025717 0015163 0 ustar 00root root 0000000 0000000 = OilyPNG {
}[https://travis-ci.org/wvanbergen/oily_png]
OilyPNG is a Ruby C extension to speed up the pure Ruby ChunkyPNG library. It is a standalone
module, so it does not require LibPNG, ImageMagick or any other library. Currently it has an
alternative implementation of decoding and encoding PNGs, making these operations much
faster, especially for PNG images that apply filtering.
Performance comparison: http://gist.github.com/611255
*Warning*: this is my first C code in years. It may blow up your PC after leaking memory all
over the place, killing a kitten in the process. You have been warned.
== Usage
1. First install the gem and make it available to your project.
2. Use require "oily_png" instead of require "chunky_png"
3. Presto! Now use ChunkyPNG as you normally would and get an instant speedup.
See http://github.com/wvanbergen/chunky_png/wiki for more information on how to use the
ChunkyPNG API.
== About
License: MIT (see LICENSE)
This C module is written by Willem van Bergen with help from Dirkjan Bussink.
oily_png-1.2.1/Rakefile 0000664 0000000 0000000 00000000730 12766025717 0015015 0 ustar 00root root 0000000 0000000 require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "rake/extensiontask"
Dir['tasks/*.rake'].each { |file| load(file) }
Rake::ExtensionTask.new('oily_png') do |ext|
ext.lib_dir = File.join('lib', 'oily_png')
ext.config_options = '--with-cflags="-std=c99"'
end
RSpec::Core::RakeTask.new(:spec) do |task|
task.pattern = "./spec/**/*_spec.rb"
task.rspec_opts = ['--color']
end
Rake::Task['spec'].prerequisites << :compile
task :default => [:spec]
oily_png-1.2.1/ext/ 0000775 0000000 0000000 00000000000 12766025717 0014150 5 ustar 00root root 0000000 0000000 oily_png-1.2.1/ext/oily_png/ 0000775 0000000 0000000 00000000000 12766025717 0015770 5 ustar 00root root 0000000 0000000 oily_png-1.2.1/ext/oily_png/color.c 0000664 0000000 0000000 00000004714 12766025717 0017260 0 ustar 00root root 0000000 0000000 #include "oily_png_ext.h"
#include
PIXEL oily_png_compose_color(PIXEL fg, PIXEL bg) {
BYTE a_com, new_r, new_g, new_b, new_a;
// Check for simple cases first
if ((A_BYTE(fg) == 0xff) || (A_BYTE(bg) == 0x00)) return fg;
if (A_BYTE(fg) == 0x00) return bg;
// Calculate the new values using fast 8-bit multiplication
a_com = INT8_MULTIPLY(0xff - A_BYTE(fg), A_BYTE(bg));
new_r = INT8_MULTIPLY(A_BYTE(fg), R_BYTE(fg)) + INT8_MULTIPLY(a_com, R_BYTE(bg));
new_g = INT8_MULTIPLY(A_BYTE(fg), G_BYTE(fg)) + INT8_MULTIPLY(a_com, G_BYTE(bg));
new_b = INT8_MULTIPLY(A_BYTE(fg), B_BYTE(fg)) + INT8_MULTIPLY(a_com, B_BYTE(bg));
new_a = A_BYTE(fg) + a_com;
return BUILD_PIXEL(new_r, new_g, new_b, new_a);
}
PIXEL oily_png_color_interpolate_quick(PIXEL fg, PIXEL bg, int alpha) {
BYTE a_com, new_r, new_g, new_b, new_a;
if (alpha >= 255) return fg;
if (alpha <= 0) return bg;
a_com = 255 - alpha;
new_r = INT8_MULTIPLY(alpha, R_BYTE(fg)) + INT8_MULTIPLY(a_com, R_BYTE(bg));
new_g = INT8_MULTIPLY(alpha, G_BYTE(fg)) + INT8_MULTIPLY(a_com, G_BYTE(bg));
new_b = INT8_MULTIPLY(alpha, B_BYTE(fg)) + INT8_MULTIPLY(a_com, B_BYTE(bg));
new_a = INT8_MULTIPLY(alpha, A_BYTE(fg)) + INT8_MULTIPLY(a_com, A_BYTE(bg));
return BUILD_PIXEL(new_r, new_g, new_b, new_a);
}
VALUE oily_png_color_compose_quick(VALUE self, VALUE fg_color, VALUE bg_color) {
UNUSED_PARAMETER(self);
return UINT2NUM(oily_png_compose_color(NUM2UINT(fg_color), NUM2UINT(bg_color)));
}
VALUE oily_png_euclidean_distance_rgba(VALUE self, VALUE color_after, VALUE color_before) {
UNUSED_PARAMETER(self);
return rb_float_new(sqrt(pow((R_BYTE(NUM2UINT(color_after)) - R_BYTE(NUM2UINT(color_before))), 2) +
pow((G_BYTE(NUM2UINT(color_after)) - G_BYTE(NUM2UINT(color_before))), 2) +
pow((B_BYTE(NUM2UINT(color_after)) - B_BYTE(NUM2UINT(color_before))), 2) +
pow((A_BYTE(NUM2UINT(color_after)) - A_BYTE(NUM2UINT(color_before))), 2)));
}
VALUE oily_png_color_r(VALUE self, VALUE value) {
UNUSED_PARAMETER(self);
return INT2FIX(R_BYTE(NUM2UINT(value)));
}
VALUE oily_png_color_g(VALUE self, VALUE value) {
UNUSED_PARAMETER(self);
return INT2FIX(G_BYTE(NUM2UINT(value)));
}
VALUE oily_png_color_b(VALUE self, VALUE value) {
UNUSED_PARAMETER(self);
return INT2FIX(B_BYTE(NUM2UINT(value)));
}
VALUE oily_png_color_a(VALUE self, VALUE value) {
UNUSED_PARAMETER(self);
return INT2FIX(A_BYTE(NUM2UINT(value)));
}
oily_png-1.2.1/ext/oily_png/color.h 0000664 0000000 0000000 00000002354 12766025717 0017263 0 ustar 00root root 0000000 0000000 #ifndef OILY_PNG_COLOR_H
#define OILY_PNG_COLOR_H
#define R_BYTE(pixel) ((BYTE) (((pixel) & (PIXEL) 0xff000000) >> 24))
#define G_BYTE(pixel) ((BYTE) (((pixel) & (PIXEL) 0x00ff0000) >> 16))
#define B_BYTE(pixel) ((BYTE) (((pixel) & (PIXEL) 0x0000ff00) >> 8))
#define A_BYTE(pixel) ((BYTE) (((pixel) & (PIXEL) 0x000000ff)))
#define BUILD_PIXEL(r, g, b, a) (((PIXEL) (r) << 24) + ((PIXEL) (g) << 16) + ((PIXEL) (b) << 8) + (PIXEL) (a))
#define INT8_MULTIPLY(a, b) (((((a) * (b) + 0x80) >> 8) + ((a) * (b) + 0x80)) >> 8)
/*
Ruby replacement method for color composition using alpha transparency.
This method should replace ChunkyPNG::Color.compose_quick
*/
VALUE oily_png_color_compose_quick(VALUE self, VALUE fg_color, VALUE bg_color);
/* Color composition using alpha transparency. */
PIXEL oily_png_compose_color(PIXEL fg, PIXEL bg);
PIXEL oily_png_color_interpolate_quick(PIXEL fg, PIXEL bg, int alpha);
/* Color comparison */
VALUE oily_png_euclidean_distance_rgba(VALUE self, VALUE color_after, VALUE color_before);
/* Accessors */
VALUE oily_png_color_r(VALUE self, VALUE pixel);
VALUE oily_png_color_g(VALUE self, VALUE pixel);
VALUE oily_png_color_b(VALUE self, VALUE pixel);
VALUE oily_png_color_a(VALUE self, VALUE pixel);
#endif
oily_png-1.2.1/ext/oily_png/extconf.rb 0000664 0000000 0000000 00000000110 12766025717 0017753 0 ustar 00root root 0000000 0000000 require 'mkmf'
$CFLAGS << ' -Wall'
create_makefile('oily_png/oily_png')
oily_png-1.2.1/ext/oily_png/oily_png_ext.c 0000664 0000000 0000000 00000005752 12766025717 0020645 0 ustar 00root root 0000000 0000000 #include "oily_png_ext.h"
void Init_oily_png() {
VALUE OilyPNG = rb_define_module("OilyPNG");
VALUE OilyPNG_Canvas = rb_define_module_under(OilyPNG, "Resampling");
rb_define_private_method(OilyPNG_Canvas, "steps_residues", oily_png_canvas_steps_residues, 2);
rb_define_private_method(OilyPNG_Canvas, "steps", oily_png_canvas_steps, 2);
rb_define_method(OilyPNG_Canvas, "resample_nearest_neighbor!", oily_png_canvas_resample_nearest_neighbor_bang, 2);
rb_define_method(OilyPNG_Canvas, "resample_bilinear!", oily_png_canvas_resample_bilinear_bang, 2);
// Setup decoding module
VALUE OilyPNG_PNGDecoding = rb_define_module_under(OilyPNG, "PNGDecoding");
rb_define_method(OilyPNG_PNGDecoding, "decode_png_image_pass", oily_png_decode_png_image_pass, 7);
// Setup encoding module
VALUE OilyPNG_PNGEncoding = rb_define_module_under(OilyPNG, "PNGEncoding");
rb_define_method(OilyPNG_PNGEncoding, "encode_png_image_pass_to_stream", oily_png_encode_png_image_pass_to_stream, 4);
// Setup Color module
VALUE OilyPNG_Color = rb_define_module_under(OilyPNG, "Color");
rb_define_method(OilyPNG_Color, "compose_quick", oily_png_color_compose_quick, 2);
rb_define_method(OilyPNG_Color, "euclidean_distance_rgba", oily_png_euclidean_distance_rgba, 2);
rb_define_method(OilyPNG_Color, "r", oily_png_color_r, 1);
rb_define_method(OilyPNG_Color, "g", oily_png_color_g, 1);
rb_define_method(OilyPNG_Color, "b", oily_png_color_b, 1);
rb_define_method(OilyPNG_Color, "a", oily_png_color_a, 1);
// Setup Operations module
VALUE OilyPNG_Operations = rb_define_module_under(OilyPNG, "Operations");
rb_define_method(OilyPNG_Operations, "compose!", oily_png_compose_bang, -1);
rb_define_method(OilyPNG_Operations, "replace!", oily_png_replace_bang, -1);
rb_define_method(OilyPNG_Operations, "rotate_left!", oily_png_rotate_left_bang, 0);
rb_define_method(OilyPNG_Operations, "rotate_right!", oily_png_rotate_right_bang, 0);
}
char oily_png_samples_per_pixel(char color_mode) {
switch (color_mode) {
case OILY_PNG_COLOR_GRAYSCALE: return 1;
case OILY_PNG_COLOR_TRUECOLOR: return 3;
case OILY_PNG_COLOR_INDEXED: return 1;
case OILY_PNG_COLOR_GRAYSCALE_ALPHA: return 2;
case OILY_PNG_COLOR_TRUECOLOR_ALPHA: return 4;
default:
rb_raise(rb_eRuntimeError, "Unsupported color mode: %d", color_mode);
return 0;
}
}
char oily_png_pixel_bitsize(char color_mode, char bit_depth) {
return oily_png_samples_per_pixel(color_mode) * bit_depth;
}
char oily_png_pixel_bytesize(char color_mode, char bit_depth) {
return (bit_depth < 8) ? 1 : (oily_png_pixel_bitsize(color_mode, bit_depth) + 7) >> 3;
}
long oily_png_scanline_bytesize(char color_mode, char bit_depth, long width) {
return (8 + ((oily_png_pixel_bitsize(color_mode, bit_depth) * width) + 7)) >> 3;
}
long oily_png_pass_bytesize(char color_mode, char bit_depth, long width, long height) {
return (width == 0 || height == 0) ? 0 : (oily_png_scanline_bytesize(color_mode, bit_depth, width)) * height;
}
oily_png-1.2.1/ext/oily_png/oily_png_ext.h 0000664 0000000 0000000 00000004124 12766025717 0020642 0 ustar 00root root 0000000 0000000 #ifndef OILY_PNG_OILY_PNG_EXT
#define OILY_PNG_OILY_PNG_EXT
#include "ruby.h"
#define RSTRING_NOT_MODIFIED
// PNG color mode constants
#define OILY_PNG_COLOR_GRAYSCALE 0
#define OILY_PNG_COLOR_TRUECOLOR 2
#define OILY_PNG_COLOR_INDEXED 3
#define OILY_PNG_COLOR_GRAYSCALE_ALPHA 4
#define OILY_PNG_COLOR_TRUECOLOR_ALPHA 6
// PNG filter constants
#define OILY_PNG_FILTER_NONE 0
#define OILY_PNG_FILTER_SUB 1
#define OILY_PNG_FILTER_UP 2
#define OILY_PNG_FILTER_AVERAGE 3
#define OILY_PNG_FILTER_PAETH 4
// Macro to surpress warnings about unused parameters.
#define UNUSED_PARAMETER(param) (void) param
// Type definitions
typedef uint32_t PIXEL; // Pixels use 32 bits unsigned integers
typedef unsigned char BYTE; // Bytes use 8 bits unsigned integers
#include "png_decoding.h"
#include "png_encoding.h"
#include "color.h"
#include "operations.h"
#include "resampling.h"
/*
Initialize the extension by creating the OilyPNG modules, and registering
the encoding and decoding replacement functions.
Note, this does not actually replace functionality in ChunkyPNG; you will need
to extend the ChunkyPNG::Canvas class with the OilyPNG::PNGDecoding module to
speed up decoding, and include OilyPNG::PNGEncoding into the same class to speed
up encoding. This is done in lib/oily_png.rb
*/
void Init_oily_png();
/*
Returns the number of samples per pixel for a given color mode
*/
char oily_png_samples_per_pixel(char color_mode);
/*
Returns the number of bits per pixel for a given color mode and bit depth.
*/
char oily_png_pixel_bitsize(char color_mode, char bit_depth);
/*
Returns the number of bytes per pixel for a given color mode and bit depth.
*/
char oily_png_pixel_bytesize(char color_mode, char bit_depth);
/*
Returns the number of bytes per scanline for a given width, color mode and bit depth.
*/
long oily_png_scanline_bytesize(char color_mode, char bit_depth, long width);
/*
Returns the number of bytes in an image pass with the given properties.
*/
long oily_png_pass_bytesize(char color_mode, char bit_depth, long width, long height);
#endif
oily_png-1.2.1/ext/oily_png/operations.c 0000664 0000000 0000000 00000016400 12766025717 0020320 0 ustar 00root root 0000000 0000000 #include "oily_png_ext.h"
void oily_png_check_size_constraints(long self_width, long self_height, long other_width, long other_height, long offset_x, long offset_y){
// For now, these raise a standard runtime error. They should however raise custom exception classes (OutOfBounds)
if(self_width < other_width + offset_x){
rb_raise(rb_eRuntimeError, "Background image width is too small!");
}
if(self_height < other_height + offset_y){
rb_raise(rb_eRuntimeError, "Background image height is too small!");
}
}
VALUE oily_png_compose_bang(int argc, VALUE *argv, VALUE self) {
// Corresponds to the other image(foreground) that we want to compose onto this one(background).
VALUE other;
// The offsets are optional arguments, so these may or may not be null pointers.
// We'll prefix them with 'opt' to identify this.
VALUE opt_offset_x;
VALUE opt_offset_y;
// Scan the passed in arguments, and populate the above-declared variables. Notice that '12'
// specifies that oily_png_compose_bang takes in 1 required parameter, and 2 optional ones (the offsets)
rb_scan_args(argc, argv, "12", &other,&opt_offset_x,&opt_offset_y);
// Regardless of whether offsets were provided, we must specify a default value for them since they will
// be used in calculating the position of the composed element.
long offset_x = 0;
long offset_y = 0;
// If offsets were provided, then the opt_offset_* variables will not be null pointers. FIXNUM_P checks
// whether they point to a fixnum object. If they do, then we can safely assign our offset_* variables to the values.
if(FIXNUM_P(opt_offset_x)){
offset_x = FIX2LONG(opt_offset_x);
}
if(FIXNUM_P(opt_offset_y)){
offset_y = FIX2LONG(opt_offset_y);
}
// Get the dimension data for both foreground and background images.
long self_width = FIX2LONG(rb_funcall(self, rb_intern("width"), 0));
long self_height = FIX2LONG(rb_funcall(self, rb_intern("height"), 0));
long other_width = FIX2LONG(rb_funcall(other, rb_intern("width"), 0));
long other_height = FIX2LONG(rb_funcall(other, rb_intern("height"), 0));
// Make sure that the 'other' image fits within the current image. If it doesn't, an exception is raised
// and the operation should be aborted.
oily_png_check_size_constraints( self_width, self_height, other_width, other_height, offset_x, offset_y );
// Get the pixel data for both the foreground(other) and background(self) pixels.
VALUE* bg_pixels = RARRAY_PTR(rb_funcall(self, rb_intern("pixels"), 0));
VALUE* fg_pixels = RARRAY_PTR(rb_funcall(other, rb_intern("pixels"), 0));
long x = 0;
long y = 0;
long bg_index = 0; // corresponds to the current index in the bg_pixels array.
for( y = 0; y < other_height; y++ ){
for( x = 0; x < other_width; x++ ){
// We need to find the value of bg_index twice, so we only calculate and store it once.
bg_index = ( x + offset_x ) + ( y + offset_y ) * self_width;
// Replace the background pixel with the composition of background + foreground
bg_pixels[bg_index] = UINT2NUM( oily_png_compose_color( NUM2UINT( fg_pixels[x+ y * other_width] ), NUM2UINT( bg_pixels[bg_index] ) ) );
}
}
return self;
}
VALUE oily_png_replace_bang(int argc, VALUE *argv, VALUE self) {
// Corresponds to the other image(foreground) that we want to compose onto this one(background).
VALUE other;
// The offsets are optional arguments, so these may or may not be null pointers.
// We'll prefix them with 'opt' to identify this.
VALUE opt_offset_x;
VALUE opt_offset_y;
// Scan the passed in arguments, and populate the above-declared variables. Notice that '12'
// specifies that oily_png_compose_bang takes in 1 required parameter, and 2 optional ones (the offsets)
rb_scan_args(argc, argv, "12", &other,&opt_offset_x,&opt_offset_y);
// Regardless of whether offsets were provided, we must specify a default value for them since they will
// be used in calculating the position of the composed element.
long offset_x = 0;
long offset_y = 0;
// If offsets were provided, then the opt_offset_* variables will not be null pointers. FIXNUM_P checks
// whether they point to a fixnum object. If they do, then we can safely assign our offset_* variables to the values.
if(FIXNUM_P(opt_offset_x)){
offset_x = FIX2LONG(opt_offset_x);
}
if(FIXNUM_P(opt_offset_y)){
offset_y = FIX2LONG(opt_offset_y);
}
// Get the dimension data for both foreground and background images.
long self_width = FIX2LONG(rb_funcall(self, rb_intern("width"), 0));
long self_height = FIX2LONG(rb_funcall(self, rb_intern("height"), 0));
long other_width = FIX2LONG(rb_funcall(other, rb_intern("width"), 0));
long other_height = FIX2LONG(rb_funcall(other, rb_intern("height"), 0));
// Make sure that the 'other' image fits within the current image. If it doesn't, an exception is raised
// and the operation should be aborted.
oily_png_check_size_constraints( self_width, self_height, other_width, other_height, offset_x, offset_y );
// Get the pixel data for both the foreground(other) and background(self) pixels.
VALUE* bg_pixels = RARRAY_PTR(rb_funcall(self, rb_intern("pixels"), 0));
VALUE* fg_pixels = RARRAY_PTR(rb_funcall(other, rb_intern("pixels"), 0));
long x = 0;
long y = 0;
long bg_index = 0; // corresponds to the current index in the bg_pixels array.
for( y = 0; y < other_height; y++ ){
for( x = 0; x < other_width; x++ ){
// We need to find the value of bg_index twice, so we only calculate and store it once.
bg_index = ( x + offset_x ) + ( y + offset_y ) * self_width;
// Replace the background pixel with the composition of background + foreground
bg_pixels[bg_index] = fg_pixels[x+ y * other_width];
}
}
return self;
}
VALUE oily_png_rotate_left_bang(VALUE self){
int store_at;
VALUE pixel_value;
int canvas_width = NUM2INT(rb_funcall(self, rb_intern("width"), 0));
int canvas_height = NUM2INT(rb_funcall(self, rb_intern("height"), 0));
VALUE original_pixels = rb_funcall(self, rb_intern("pixels"), 0);
VALUE new_pixels = rb_ary_dup(original_pixels);
int i, j;
for( j = 0 ; j < canvas_width; j++ ){
for( i = 0 ; i < canvas_height; i++ ){
store_at = (canvas_width - 1 - j)*canvas_height + i;
pixel_value = rb_ary_entry(original_pixels, i*canvas_width + j);
rb_ary_store(new_pixels, store_at, pixel_value );
}
}
rb_funcall(self, rb_intern("replace_canvas!"), 3, INT2NUM(canvas_height), INT2NUM(canvas_width), new_pixels);
return self;
}
VALUE oily_png_rotate_right_bang(VALUE self){
int store_at;
VALUE pixel_value;
int canvas_width = NUM2INT(rb_funcall(self, rb_intern("width"), 0));
int canvas_height = NUM2INT(rb_funcall(self, rb_intern("height"), 0));
VALUE original_pixels = rb_funcall(self, rb_intern("pixels"), 0);
VALUE new_pixels = rb_ary_dup(original_pixels);
int i, j;
for( j = 0; j < canvas_width; j++ ){
for( i = 0; i < canvas_height; i++ ){
store_at = j * canvas_height + (canvas_height - i - 1);
pixel_value = rb_ary_entry(original_pixels, i*canvas_width + j);
rb_ary_store(new_pixels, store_at, pixel_value );
}
}
rb_funcall(self, rb_intern("replace_canvas!"), 3, INT2NUM(canvas_height), INT2NUM(canvas_width), new_pixels);
return self;
} oily_png-1.2.1/ext/oily_png/operations.h 0000664 0000000 0000000 00000003435 12766025717 0020331 0 ustar 00root root 0000000 0000000 #ifndef OILY_PNG_OPERATIONS_H
#define OILY_PNG_OPERATIONS_H
/*
Checks whether an image 'other' can fits into 'self'. Takes offset into account.
An exception is raised if the check fails.
Instead of taking in an object 'self' and an object 'other' and then calculating their parameters,
we ask for the respective height and width directly. This is because these variables will need to be calculated
by 'rb_intern()' within the method calling oily_png_check_size_constraints (ex: oily_png_compose), so there's no
use in calculating them twice.
*/
void oily_png_check_size_constraints(long self_width, long self_height, long other_width, long other_height, long offset_x, long offset_y);
/*
C replacement method for composing another image onto this image using alpha blending.
TODO: Implement functionality with ChunkyPNG and OilyPNG so that an image can be composited onto another
regardless of its size: however, only the intersecting elements of both images should be mixed.
This method should replace ChunkyPNG::Canvas.compose!
*/
VALUE oily_png_compose_bang(int argc, VALUE *argv, VALUE c);
/*
C replacement method for composing another image onto this image by simply replacing pixels.
TODO: Implement functionality with ChunkyPNG and OilyPNG so that an image can be composited onto another
regardless of its size: however, only the intersecting elements of both images should be mixed.
This method should replace ChunkyPNG::Canvas.replace!
*/
VALUE oily_png_replace_bang(int argc, VALUE *argv, VALUE c);
/*
C replacement method for rotating the image 90 degrees counter-clockwise.
*/
VALUE oily_png_rotate_left_bang(VALUE self);
/*
C replacement method for rotating the image 90 degrees clockwise.
*/
VALUE oily_png_rotate_right_bang(VALUE self);
#endif
oily_png-1.2.1/ext/oily_png/png_decoding.c 0000664 0000000 0000000 00000034105 12766025717 0020557 0 ustar 00root root 0000000 0000000 #include "oily_png_ext.h"
/////////////////////////////////////////////////////////////////////
// UNFILTERING SCANLINES
/////////////////////////////////////////////////////////////////////
// Decodes a SUB filtered scanline at the given position in the byte array
void oily_png_decode_filter_sub(BYTE* bytes, long pos, long line_length, char pixel_size) {
long i;
for (i = 1 + pixel_size; i < line_length; i++) {
UNFILTER_BYTE(bytes[pos + i], bytes[pos + i - pixel_size]);
}
}
// Decodes an UP filtered scanline at the given position in the byte array
void oily_png_decode_filter_up(BYTE* bytes, long pos, long line_size, char pixel_size) {
UNUSED_PARAMETER(pixel_size);
long i;
if (pos >= line_size) { // The first line is not filtered because there is no privous line
for (i = 1; i < line_size; i++) {
UNFILTER_BYTE(bytes[pos + i], bytes[pos + i - line_size]);
}
}
}
// Decodes an AVERAGE filtered scanline at the given position in the byte array
void oily_png_decode_filter_average(BYTE* bytes, long pos, long line_size, char pixel_size) {
long i;
BYTE a, b;
for (i = 1; i < line_size; i++) {
a = (i > pixel_size) ? bytes[pos + i - pixel_size] : 0;
b = (pos >= line_size) ? bytes[pos + i - line_size] : 0;
UNFILTER_BYTE(bytes[pos + i], (a + b) >> 1);
}
}
// Decodes a PAETH filtered scanline at the given position in the byte array
void oily_png_decode_filter_paeth(BYTE* bytes, long pos, long line_size, char pixel_size) {
BYTE a, b, c, pr;
long i, p, pa, pb, pc;
for (i = 1; i < line_size; i++) {
a = (i > pixel_size) ? bytes[pos + i - pixel_size] : 0;
b = (pos >= line_size) ? bytes[pos + i - line_size] : 0;
c = (pos >= line_size && i > pixel_size) ? bytes[pos + i - line_size - pixel_size] : 0;
p = a + b - c;
pa = (p > a) ? p - a : a - p;
pb = (p > b) ? p - b : b - p;
pc = (p > c) ? p - c : c - p;
pr = (pa <= pb) ? (pa <= pc ? a : c) : (pb <= pc ? b : c);
UNFILTER_BYTE(bytes[pos + i], pr);
}
}
/////////////////////////////////////////////////////////////////////
// BIT HANDLING
/////////////////////////////////////////////////////////////////////
BYTE oily_png_extract_1bit_element(BYTE* bytes, long start, long x) {
BYTE byte = bytes[start + 1 + (x >> 3)];
char bitshift = 7 - (x & (BYTE) 0x07);
return (byte & (0x01 << bitshift)) >> bitshift;
}
BYTE oily_png_extract_2bit_element(BYTE* bytes, long start, long x) {
BYTE byte = bytes[start + 1 + (x >> 2)];
char bitshift = (6 - ((x & (BYTE) 0x03) << 1));
return (byte & (0x03 << bitshift)) >> bitshift;
}
BYTE oily_png_extract_4bit_element(BYTE* bytes, long start, long x) {
return ((x & 0x01) == 0) ? ((bytes[(start) + 1 + ((x) >> 1)] & (BYTE) 0xf0) >> 4) : (bytes[(start) + 1 + ((x) >> 1)] & (BYTE) 0x0f);
}
BYTE oily_png_resample_1bit_element(BYTE* bytes, long start, long x) {
BYTE value = oily_png_extract_1bit_element(bytes, start, x);
return (value == 0) ? 0x00 : 0xff;
}
BYTE oily_png_resample_2bit_element(BYTE* bytes, long start, long x) {
return oily_png_extract_2bit_element(bytes, start, x) * 85;
}
BYTE oily_png_resample_4bit_element(BYTE* bytes, long start, long x) {
return oily_png_extract_4bit_element(bytes, start, x) * 17;
}
/////////////////////////////////////////////////////////////////////
// PIXEL DECODING SCANLINES
/////////////////////////////////////////////////////////////////////
void oily_png_decode_scanline_grayscale_1bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
UNUSED_PARAMETER(decoding_palette);
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_RGBA(pixels,
oily_png_resample_1bit_element(bytes, start, x),
oily_png_resample_1bit_element(bytes, start, x),
oily_png_resample_1bit_element(bytes, start, x),
0xff);
}
}
void oily_png_decode_scanline_grayscale_2bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
UNUSED_PARAMETER(decoding_palette);
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_RGBA(pixels,
oily_png_resample_2bit_element(bytes, start, x),
oily_png_resample_2bit_element(bytes, start, x),
oily_png_resample_2bit_element(bytes, start, x),
0xff);
}
}
void oily_png_decode_scanline_grayscale_4bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
UNUSED_PARAMETER(decoding_palette);
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_RGBA(pixels,
oily_png_resample_4bit_element(bytes, start, x),
oily_png_resample_4bit_element(bytes, start, x),
oily_png_resample_4bit_element(bytes, start, x),
0xff);
}
}
void oily_png_decode_scanline_grayscale_8bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
UNUSED_PARAMETER(decoding_palette);
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_RGBA(pixels,
bytes[start + 1 + x],
bytes[start + 1 + x],
bytes[start + 1 + x],
0xff);
}
}
void oily_png_decode_scanline_grayscale_16bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
UNUSED_PARAMETER(decoding_palette);
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_RGBA(pixels,
bytes[start + 1 + (x * 2)],
bytes[start + 1 + (x * 2)],
bytes[start + 1 + (x * 2)],
0xff);
}
}
void oily_png_decode_scanline_grayscale_alpha_8bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
UNUSED_PARAMETER(decoding_palette);
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_RGBA(pixels,
bytes[start + 1 + (x * 2) + 0],
bytes[start + 1 + (x * 2) + 0],
bytes[start + 1 + (x * 2) + 0],
bytes[start + 1 + (x * 2) + 1]);
}
}
void oily_png_decode_scanline_grayscale_alpha_16bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
UNUSED_PARAMETER(decoding_palette);
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_RGBA(pixels,
bytes[start + 1 + (x * 4) + 0],
bytes[start + 1 + (x * 4) + 0],
bytes[start + 1 + (x * 4) + 0],
bytes[start + 1 + (x * 4) + 2]);
}
}
void oily_png_decode_scanline_indexed_1bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_PALLETE(pixels, decoding_palette, oily_png_extract_1bit_element(bytes, start, x));
}
}
void oily_png_decode_scanline_indexed_2bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_PALLETE(pixels, decoding_palette, oily_png_extract_2bit_element(bytes, start, x));
}
}
void oily_png_decode_scanline_indexed_4bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_PALLETE(pixels, decoding_palette, oily_png_extract_4bit_element(bytes, start, x));
}
}
void oily_png_decode_scanline_indexed_8bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_PALLETE(pixels, decoding_palette, bytes[start + 1 + x]);
}
}
void oily_png_decode_scanline_truecolor_8bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
UNUSED_PARAMETER(decoding_palette);
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_RGBA(pixels,
bytes[start + 1 + (x * 3) + 0],
bytes[start + 1 + (x * 3) + 1],
bytes[start + 1 + (x * 3) + 2],
0xff);
}
}
void oily_png_decode_scanline_truecolor_16bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
UNUSED_PARAMETER(decoding_palette);
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_RGBA(pixels,
bytes[start + 1 + (x * 6) + 0],
bytes[start + 1 + (x * 6) + 2],
bytes[start + 1 + (x * 6) + 4],
0xff);
}
}
void oily_png_decode_scanline_truecolor_alpha_8bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
UNUSED_PARAMETER(decoding_palette);
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_RGBA(pixels,
bytes[start + 1 + (x * 4) + 0],
bytes[start + 1 + (x * 4) + 1],
bytes[start + 1 + (x * 4) + 2],
bytes[start + 1 + (x * 4) + 3]);
}
}
void oily_png_decode_scanline_truecolor_alpha_16bit(VALUE pixels, BYTE* bytes, long start, long width, VALUE decoding_palette) {
UNUSED_PARAMETER(decoding_palette);
long x;
for (x = 0; x < width; x++) {
ADD_PIXEL_FROM_RGBA(pixels,
bytes[start + 1 + (x * 8) + 0],
bytes[start + 1 + (x * 8) + 2],
bytes[start + 1 + (x * 8) + 4],
bytes[start + 1 + (x * 8) + 6]);
}
}
scanline_decoder_func oily_png_decode_scanline_func(int color_mode, int bit_depth) {
switch (color_mode) {
case OILY_PNG_COLOR_GRAYSCALE:
switch (bit_depth) {
case 1: return &oily_png_decode_scanline_grayscale_1bit;
case 2: return &oily_png_decode_scanline_grayscale_2bit;
case 4: return &oily_png_decode_scanline_grayscale_4bit;
case 8: return &oily_png_decode_scanline_grayscale_8bit;
case 16: return &oily_png_decode_scanline_grayscale_16bit;
default: return NULL;
}
case OILY_PNG_COLOR_TRUECOLOR:
switch (bit_depth) {
case 8: return &oily_png_decode_scanline_truecolor_8bit;
case 16: return &oily_png_decode_scanline_truecolor_16bit;
default: return NULL;
}
case OILY_PNG_COLOR_INDEXED:
switch (bit_depth) {
case 1: return &oily_png_decode_scanline_indexed_1bit;
case 2: return &oily_png_decode_scanline_indexed_2bit;
case 4: return &oily_png_decode_scanline_indexed_4bit;
case 8: return &oily_png_decode_scanline_indexed_8bit;
default: return NULL;
}
case OILY_PNG_COLOR_GRAYSCALE_ALPHA:
switch (bit_depth) {
case 8: return &oily_png_decode_scanline_grayscale_alpha_8bit;
case 16: return &oily_png_decode_scanline_grayscale_alpha_16bit;
default: return NULL;
}
case OILY_PNG_COLOR_TRUECOLOR_ALPHA:
switch (bit_depth) {
case 8: return &oily_png_decode_scanline_truecolor_alpha_8bit;
case 16: return &oily_png_decode_scanline_truecolor_alpha_16bit;
default: return NULL;
}
default: return NULL;
}
}
/////////////////////////////////////////////////////////////////////
// DECODING AN IMAGE PASS
/////////////////////////////////////////////////////////////////////
VALUE oily_png_decode_palette(VALUE decoding_palette) {
if (decoding_palette != Qnil) {
VALUE decoding_map = rb_iv_get(decoding_palette, "@decoding_map");
if (rb_funcall(decoding_map, rb_intern("kind_of?"), 1, rb_cArray) == Qtrue) {
return decoding_map;
}
}
rb_raise(rb_eRuntimeError, "Could not retrieve a decoding palette for this image!");
return Qnil;
}
VALUE oily_png_decode_png_image_pass(VALUE self, VALUE stream, VALUE width, VALUE height, VALUE color_mode, VALUE depth, VALUE start_pos, VALUE decoding_palette) {
VALUE pixels = rb_ary_new();
if ((FIX2LONG(height) > 0) && (FIX2LONG(width) > 0)) {
char pixel_size = oily_png_pixel_bytesize(FIX2INT(color_mode), FIX2INT(depth));
long line_size = oily_png_scanline_bytesize(FIX2INT(color_mode), FIX2INT(depth), FIX2LONG(width));
long pass_size = oily_png_pass_bytesize(FIX2INT(color_mode), FIX2INT(depth), FIX2LONG(width), FIX2LONG(height));
// Make sure that the stream is large enough to contain our pass.
if (RSTRING_LEN(stream) < pass_size + FIX2LONG(start_pos)) {
rb_raise(rb_eRuntimeError, "The length of the stream is too short to contain the image!");
}
// Copy the bytes for this pass from the stream to a separate location
// so we can work on this byte array directly.
BYTE* bytes = ALLOC_N(BYTE, pass_size);
memcpy(bytes, RSTRING_PTR(stream) + FIX2LONG(start_pos), pass_size);
// Get the decoding palette for indexed images.
VALUE decoding_map = Qnil;
if (FIX2INT(color_mode) == OILY_PNG_COLOR_INDEXED) {
decoding_map = oily_png_decode_palette(decoding_palette);
}
// Select the scanline decoder function for this color mode and bit depth.
scanline_decoder_func scanline_decoder = oily_png_decode_scanline_func(FIX2INT(color_mode), FIX2INT(depth));
if (scanline_decoder == NULL) {
rb_raise(rb_eRuntimeError, "No decoder for color mode %d and bit depth %d", FIX2INT(color_mode), FIX2INT(depth));
}
long y, line_start;
for (y = 0; y < FIX2LONG(height); y++) {
line_start = y * line_size;
// Apply filering to the line
switch (bytes[line_start]) {
case OILY_PNG_FILTER_NONE: break;
case OILY_PNG_FILTER_SUB: oily_png_decode_filter_sub( bytes, line_start, line_size, pixel_size); break;
case OILY_PNG_FILTER_UP: oily_png_decode_filter_up( bytes, line_start, line_size, pixel_size); break;
case OILY_PNG_FILTER_AVERAGE: oily_png_decode_filter_average( bytes, line_start, line_size, pixel_size); break;
case OILY_PNG_FILTER_PAETH: oily_png_decode_filter_paeth( bytes, line_start, line_size, pixel_size); break;
default: rb_raise(rb_eRuntimeError, "Filter type not supported: %d", bytes[line_start]);
}
// Set the filter byte to 0 because the bytearray is now unfiltered.
bytes[line_start] = OILY_PNG_FILTER_NONE;
scanline_decoder(pixels, bytes, line_start, FIX2LONG(width), decoding_map);
}
xfree(bytes);
}
// Now, return a new ChunkyPNG::Canvas instance with the decoded pixels.
return rb_funcall(self, rb_intern("new"), 3, width, height, pixels);
}
oily_png-1.2.1/ext/oily_png/png_decoding.h 0000664 0000000 0000000 00000002233 12766025717 0020561 0 ustar 00root root 0000000 0000000 #ifndef OILY_PNG_PNG_DECODING_H
#define OILY_PNG_PNG_DECODING_H
#define UNFILTER_BYTE(byte, adjustment) byte = (BYTE) (((byte) + (adjustment)) & 0x000000ff)
#define ADD_PIXEL_FROM_PALLETE(pixels, decoding_palette, palette_entry) \
if (RARRAY_LEN(decoding_palette) > (palette_entry)) { \
rb_ary_push(pixels, rb_ary_entry(decoding_palette, (palette_entry))); \
} else { \
rb_raise(rb_eRuntimeError, "The decoding palette does not have %d entries!", (palette_entry)); \
}
#define ADD_PIXEL_FROM_RGBA(pixels, r, g, b, a) rb_ary_push(pixels, UINT2NUM(BUILD_PIXEL(r,g,b,a)));
typedef void(*scanline_decoder_func)(VALUE, BYTE*, long, long, VALUE);
/*
Decodes an image pass from the given byte stream at the given position.
A normal PNG will only have one pass that consumes the entire stream, while an
interlaced image requires 7 passes which are loaded from different starting positions.
This function shouild replace ChunkyPNG::Canvas::PNGDecoding.decode_png_image_pass
*/
VALUE oily_png_decode_png_image_pass(VALUE self, VALUE stream, VALUE width, VALUE height, VALUE color_mode, VALUE depth, VALUE start_pos, VALUE decoding_palette);
#endif
oily_png-1.2.1/ext/oily_png/png_encoding.c 0000664 0000000 0000000 00000030722 12766025717 0020572 0 ustar 00root root 0000000 0000000 #include "oily_png_ext.h"
///// 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) {
UNUSED_PARAMETER(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);
}
}
///// Scanline encoding functions //////////////////////////////////////////
// Assume R == G == B. ChunkyPNG uses the B byte fot performance reasons.
// We'll uses the same to remain compatible with ChunkyPNG.
void oily_png_encode_scanline_grayscale_1bit(BYTE* bytes, VALUE pixels, long y, long width, VALUE encoding_palette) {
UNUSED_PARAMETER(encoding_palette);
long x; BYTE p1, p2, p3, p4, p5, p6, p7, p8;
for (x = 0; x < width; x += 8) {
p1 = (x + 0 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 0))) >> 7);
p2 = (x + 1 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 1))) >> 7);
p3 = (x + 2 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 2))) >> 7);
p4 = (x + 3 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 3))) >> 7);
p5 = (x + 4 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 4))) >> 7);
p6 = (x + 5 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 5))) >> 7);
p7 = (x + 6 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 6))) >> 7);
p8 = (x + 7 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 7))) >> 7);
bytes[x >> 3] = (BYTE) ((p1 << 7) | (p2 << 6) | (p3 << 5) | (p4 << 4) | (p5 << 3) | (p6 << 2) | (p7 << 1) | (p8));
}
}
// Assume R == G == B. ChunkyPNG uses the B byte fot performance reasons.
// We'll uses the same to remain compatible with ChunkyPNG.
void oily_png_encode_scanline_grayscale_2bit(BYTE* bytes, VALUE pixels, long y, long width, VALUE encoding_palette) {
UNUSED_PARAMETER(encoding_palette);
long x; BYTE p1, p2, p3, p4;
for (x = 0; x < width; x += 4) {
p1 = (x + 0 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 0))) >> 6);
p2 = (x + 1 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 1))) >> 6);
p3 = (x + 2 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 2))) >> 6);
p4 = (x + 3 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 3))) >> 6);
bytes[x >> 2] = (BYTE) ((p1 << 6) | (p2 << 4) | (p3 << 2) | (p4));
}
}
// Assume R == G == B. ChunkyPNG uses the B byte fot performance reasons.
// We'll uses the same to remain compatible with ChunkyPNG.
void oily_png_encode_scanline_grayscale_4bit(BYTE* bytes, VALUE pixels, long y, long width, VALUE encoding_palette) {
UNUSED_PARAMETER(encoding_palette);
long x; BYTE p1, p2;
for (x = 0; x < width; x += 2) {
p1 = (x + 0 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 0))) >> 4);
p2 = (x + 1 >= width) ? 0 : (B_BYTE(NUM2UINT(rb_ary_entry(pixels, y * width + x + 1))) >> 4);
bytes[x >> 1] = (BYTE) ((p1 << 4) | (p2));
}
}
// Assume R == G == B. ChunkyPNG uses the B byte fot performance reasons.
// We'll uses the same to remain compatible with ChunkyPNG.
void oily_png_encode_scanline_grayscale_8bit(BYTE* bytes, VALUE pixels, long y, long width, VALUE encoding_palette) {
UNUSED_PARAMETER(encoding_palette);
long x; PIXEL pixel;
for (x = 0; x < width; x++) {
pixel = NUM2UINT(rb_ary_entry(pixels, y * width + x));
bytes[x] = B_BYTE(pixel);
}
}
// Assume R == G == B. ChunkyPNG uses the B byte fot performance reasons.
// We'll uses the same to remain compatible with ChunkyPNG.
void oily_png_encode_scanline_grayscale_alpha_8bit(BYTE* bytes, VALUE pixels, long y, long width, VALUE encoding_palette) {
UNUSED_PARAMETER(encoding_palette);
long x; PIXEL pixel;
for (x = 0; x < width; x++) {
pixel = NUM2UINT(rb_ary_entry(pixels, y * width + x));
bytes[x * 2 + 0] = B_BYTE(pixel);
bytes[x * 2 + 1] = A_BYTE(pixel);
}
}
void oily_png_encode_scanline_indexed_8bit(BYTE* bytes, VALUE pixels, long y, long width, VALUE encoding_palette) {
long x;
for (x = 0; x < width; x++) {
bytes[x] = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x);
}
}
void oily_png_encode_scanline_indexed_4bit(BYTE* bytes, VALUE pixels, long y, long width, VALUE encoding_palette) {
long x; BYTE p1, p2;
for (x = 0; x < width; x += 2) {
p1 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 0);
p2 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 1);
bytes[x >> 1] = (BYTE) ((p1 << 4) | (p2));
}
}
void oily_png_encode_scanline_indexed_2bit(BYTE* bytes, VALUE pixels, long y, long width, VALUE encoding_palette) {
long x; BYTE p1, p2, p3, p4;
for (x = 0; x < width; x += 4) {
p1 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 0);
p2 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 1);
p3 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 2);
p4 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 3);
bytes[x >> 2] = (BYTE) ((p1 << 6) | (p2 << 4) | (p3 << 2) | (p4));
}
}
void oily_png_encode_scanline_indexed_1bit(BYTE* bytes, VALUE pixels, long y, long width, VALUE encoding_palette) {
long x; BYTE p1, p2, p3, p4, p5, p6, p7, p8;
for (x = 0; x < width; x += 8) {
p1 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 0);
p2 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 1);
p3 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 2);
p4 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 3);
p5 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 4);
p6 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 5);
p7 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 6);
p8 = ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x + 7);
bytes[x >> 3] = (BYTE) ((p1 << 7) | (p2 << 6) | (p3 << 5) | (p4 << 4) | (p5 << 3) | (p6 << 2) | (p7 << 1) | (p8));
}
}
void oily_png_encode_scanline_truecolor_8bit(BYTE* bytes, VALUE pixels, long y, long width, VALUE encoding_palette) {
UNUSED_PARAMETER(encoding_palette);
long x; PIXEL pixel;
for (x = 0; x < width; x++) {
pixel = NUM2UINT(rb_ary_entry(pixels, y * width + x));
bytes[x * 3 + 0] = R_BYTE(pixel);
bytes[x * 3 + 1] = G_BYTE(pixel);
bytes[x * 3 + 2] = B_BYTE(pixel);
}
}
void oily_png_encode_scanline_truecolor_alpha_8bit(BYTE* bytes, VALUE pixels, long y, long width, VALUE encoding_palette) {
UNUSED_PARAMETER(encoding_palette);
long x; PIXEL pixel;
for (x = 0; x < width; x++) {
pixel = NUM2UINT(rb_ary_entry(pixels, y * width + x));
bytes[x * 4 + 0] = R_BYTE(pixel);
bytes[x * 4 + 1] = G_BYTE(pixel);
bytes[x * 4 + 2] = B_BYTE(pixel);
bytes[x * 4 + 3] = A_BYTE(pixel);
}
}
scanline_encoder_func oily_png_encode_scanline_func(char color_mode, char bit_depth) {
switch (color_mode) {
case OILY_PNG_COLOR_GRAYSCALE:
switch (bit_depth) {
case 8: return &oily_png_encode_scanline_grayscale_8bit;
case 4: return &oily_png_encode_scanline_grayscale_4bit;
case 2: return &oily_png_encode_scanline_grayscale_2bit;
case 1: return &oily_png_encode_scanline_grayscale_1bit;
default: return NULL;
}
case OILY_PNG_COLOR_GRAYSCALE_ALPHA:
switch (bit_depth) {
case 8: return &oily_png_encode_scanline_grayscale_alpha_8bit;
default: return NULL;
}
case OILY_PNG_COLOR_INDEXED:
switch (bit_depth) {
case 8: return &oily_png_encode_scanline_indexed_8bit;
case 4: return &oily_png_encode_scanline_indexed_4bit;
case 2: return &oily_png_encode_scanline_indexed_2bit;
case 1: return &oily_png_encode_scanline_indexed_1bit;
default: return NULL;
}
case OILY_PNG_COLOR_TRUECOLOR:
switch (bit_depth) {
case 8: return &oily_png_encode_scanline_truecolor_8bit;
default: return NULL;
}
case OILY_PNG_COLOR_TRUECOLOR_ALPHA:
switch (bit_depth) {
case 8: return &oily_png_encode_scanline_truecolor_alpha_8bit;
default: return NULL;
}
default: return NULL;
}
}
/////////////////////////////////////////////////////////////////////
// ENCODING AN IMAGE PASS
/////////////////////////////////////////////////////////////////////
VALUE oily_png_encode_palette(VALUE self) {
VALUE palette_instance = rb_funcall(self, rb_intern("encoding_palette"), 0);
if (palette_instance != Qnil) {
VALUE encoding_map = rb_iv_get(palette_instance, "@encoding_map");
if (rb_funcall(encoding_map, rb_intern("kind_of?"), 1, rb_cHash) == Qtrue) {
return encoding_map;
}
}
rb_raise(rb_eRuntimeError, "Could not retrieve a decoding palette for this image!");
return Qnil;
}
VALUE oily_png_encode_png_image_pass_to_stream(VALUE self, VALUE stream, VALUE color_mode, VALUE bit_depth, VALUE filtering) {
UNUSED_PARAMETER(bit_depth);
// Get the data
char depth = (char) FIX2INT(bit_depth);
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 encoding_palette = Qnil;
if (FIX2INT(color_mode) == OILY_PNG_COLOR_INDEXED) {
encoding_palette = oily_png_encode_palette(self);
}
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 = ALLOC_N(BYTE, pass_size);
// Get the scanline encoder function.
scanline_encoder_func scanline_encoder = oily_png_encode_scanline_func(FIX2INT(color_mode), depth);
if (scanline_encoder == NULL) {
rb_raise(rb_eRuntimeError, "No encoder for color mode %d and bit depth %d", FIX2INT(color_mode), depth);
}
long y, pos;
for (y = height - 1; y >= 0; y--) {
pos = line_size * y;
bytes[pos] = (BYTE) FIX2INT(filtering);
scanline_encoder(bytes + pos + 1, pixels, y, width, encoding_palette);
}
if (FIX2INT(filtering) != OILY_PNG_FILTER_NONE) {
// Get the scanline filter function
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));
}
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);
xfree(bytes);
return Qnil;
}
oily_png-1.2.1/ext/oily_png/png_encoding.h 0000664 0000000 0000000 00000001615 12766025717 0020576 0 ustar 00root root 0000000 0000000 #ifndef OILY_PNG_PNG_ENCODING_H
#define OILY_PNG_PNG_ENCODING_H
#define FILTER_BYTE(byte, adjustment) byte = (BYTE) (((byte) - (adjustment)) & 0x000000ff)
#define ENCODING_PALETTE_INDEX(encoding_palette, pixels, width, y, x) (((x) < (width)) ? ((BYTE) NUM2UINT(rb_hash_aref(encoding_palette, rb_ary_entry(pixels, (y) * (width) + (x))))) : 0)
typedef void(*scanline_encoder_func)(BYTE*, VALUE, long, long, VALUE);
/*
Encodes an image and append it to the stream.
A normal PNG will only have one pass and call this method once, while interlaced
images are split up in 7 distinct images. This method will be called for every one
of these images, reusing the stream.
This function should replace ChunkyPNG::Canvas::PNGEncoding.encode_png_image_pass_to_stream
*/
VALUE oily_png_encode_png_image_pass_to_stream(VALUE self, VALUE stream, VALUE color_mode, VALUE bit_depth, VALUE filtering);
#endif
oily_png-1.2.1/ext/oily_png/resampling.c 0000664 0000000 0000000 00000014241 12766025717 0020277 0 ustar 00root root 0000000 0000000 #include "oily_png_ext.h"
#include
void oily_png_generate_steps_residues(long width, long new_width, long *steps, long *residues) {
long base_step = width / new_width;
long err_step = (width % new_width) << 1;
long denominator = new_width << 1;
long index;
long err;
/* We require an arithmetic modolus and rounding to the left of zero
* This is standard Ruby behaviour (I hope!) but differs with how C/Java
* typically handle integer division and modulo. But since we are workig
* in mixed numbers, Ruby's convention is especially convienent */
if (width >= new_width) {
index = (width - new_width) / denominator;
err = (width - new_width) % denominator;
} else {
index = (width - new_width) / denominator - 1;
err = denominator - ((new_width - width) % denominator);
}
long i;
for (i=0; i < new_width; i++){
if (residues != NULL) {
steps[i] = index;
residues[i] = (long) round(255.0 * (float) err / (float) denominator);
} else {
/* If residues aren't requested, we round to the nearest pixel */
if (err < new_width) {
steps[i] = index;
} else {
steps[i] = index + 1;
}
}
index += base_step;
err += err_step;
if (err >= denominator) {
index += 1;
err -= denominator;
}
}
}
VALUE oily_png_canvas_steps(VALUE self, VALUE v_width, VALUE v_new_width) {
UNUSED_PARAMETER(self);
long width = NUM2LONG(v_width);
long new_width = NUM2LONG(v_new_width);
long *steps = ALLOC_N(long, new_width);
VALUE ret = rb_ary_new2(new_width);
oily_png_generate_steps_residues(width, new_width, steps, NULL);
long i;
for (i=0; i < new_width; i++) {
rb_ary_store(ret, i, LONG2FIX(steps[i]));
}
/* This is an unprotected allocation; it will leak on exception.
* However, rb_ary_store should not generate one as we have
* pre-allocated the array.
*/
xfree(steps);
steps = NULL;
return ret;
}
VALUE oily_png_canvas_steps_residues(VALUE self, VALUE v_width, VALUE v_new_width) {
UNUSED_PARAMETER(self);
long width = NUM2LONG(v_width);
long new_width = NUM2LONG(v_new_width);
VALUE ret_steps = rb_ary_new2(new_width);
VALUE ret_residues = rb_ary_new2(new_width);
long *steps = ALLOC_N(long, new_width);
long *residues = ALLOC_N(long, new_width);
oily_png_generate_steps_residues(width, new_width, steps, residues);
long i;
for (i=0; i < new_width; i++) {
rb_ary_store(ret_steps, i, LONG2FIX(steps[i]));
rb_ary_store(ret_residues, i, LONG2FIX(residues[i]));
}
/* This is an unprotected allocation; it will leak on exception.
* However, rb_ary_store should not generate one as we have
* pre-allocated the array.
*/
xfree(steps);
steps = NULL;
xfree(residues);
residues = NULL;
/* We return multiple values */
VALUE ret = rb_ary_new2(2);
rb_ary_store(ret, 0, ret_steps);
rb_ary_store(ret, 1, ret_residues);
return ret;
}
VALUE oily_png_canvas_resample_nearest_neighbor_bang(VALUE self, VALUE v_new_width, VALUE v_new_height) {
long new_width = NUM2LONG(v_new_width);
long new_height = NUM2LONG(v_new_height);
long self_width = NUM2LONG(rb_funcall(self, rb_intern("width"), 0));
long self_height = NUM2LONG(rb_funcall(self, rb_intern("height"), 0));
VALUE pixels = rb_ary_new2(new_width*new_height);
VALUE source = rb_iv_get(self, "@pixels");
long *steps_x = ALLOC_N(long, new_width);
long *steps_y = ALLOC_N(long, new_height);
oily_png_generate_steps_residues(self_width, new_width, steps_x, NULL);
oily_png_generate_steps_residues(self_height, new_height, steps_y, NULL);
long index = 0;
long x, y;
long src_index;
for (y=0; y < new_height; y++) {
for (x = 0; x < new_width; x++) {
src_index = steps_y[y] * self_width + steps_x[x];
VALUE pixel = rb_ary_entry(source, src_index);
rb_ary_store(pixels, index, pixel);
index++;
}
}
xfree(steps_x);
steps_x = NULL;
xfree(steps_y);
steps_y = NULL;
rb_iv_set(self, "@pixels", pixels);
rb_iv_set(self, "@width", LONG2NUM(new_width));
rb_iv_set(self, "@height", LONG2NUM(new_height));
return self;
}
VALUE oily_png_canvas_resample_bilinear_bang(VALUE self, VALUE v_new_width, VALUE v_new_height) {
long new_width = NUM2LONG(v_new_width);
long new_height = NUM2LONG(v_new_height);
long self_width = NUM2LONG(rb_funcall(self, rb_intern("width"), 0));
long self_height = NUM2LONG(rb_funcall(self, rb_intern("height"), 0));
VALUE pixels = rb_ary_new2(new_width*new_height);
VALUE source = rb_iv_get(self, "@pixels");
long *index_x = ALLOC_N(long, new_width);
long *index_y = ALLOC_N(long, new_height);
long *interp_x = ALLOC_N(long, new_width);
long *interp_y = ALLOC_N(long, new_height);
oily_png_generate_steps_residues(self_width, new_width, index_x, interp_x);
oily_png_generate_steps_residues(self_height, new_height, index_y, interp_y);
long index = 0;
long x, y;
long y1, y2, x1, x2;
PIXEL y_residue, x_residue;
PIXEL pixel_11, pixel_21, pixel_12, pixel_22;
PIXEL pixel_top, pixel_bot;
for (y = 0; y < new_height; y++) {
y1 = index_y[y] < 0 ? 0 : index_y[y];
y2 = index_y[y]+1 >= self_height ? self_height-1 : index_y[y]+1;
y_residue = interp_y[y];
for (x = 0; x < new_width; x++) {
x1 = index_x[x] < 0 ? 0 : index_x[x];
x2 = index_x[x]+1 >= self_width ? self_width-1 : index_x[x]+1;
x_residue = interp_x[x];
pixel_11 = NUM2UINT(rb_ary_entry(source, y1*self_width + x1));
pixel_21 = NUM2UINT(rb_ary_entry(source, y1*self_width + x2));
pixel_12 = NUM2UINT(rb_ary_entry(source, y2*self_width + x1));
pixel_22 = NUM2UINT(rb_ary_entry(source, y2*self_width + x2));
pixel_top = oily_png_color_interpolate_quick(pixel_21, pixel_11, x_residue);
pixel_bot = oily_png_color_interpolate_quick(pixel_22, pixel_12, x_residue);
rb_ary_store(pixels, index++, UINT2NUM(oily_png_color_interpolate_quick(pixel_bot, pixel_top, y_residue)));
}
}
xfree(index_x);
xfree(index_y);
xfree(interp_x);
xfree(interp_y);
interp_x = NULL;
interp_y = NULL;
rb_iv_set(self, "@pixels", pixels);
rb_iv_set(self, "@width", LONG2NUM(new_width));
rb_iv_set(self, "@height", LONG2NUM(new_height));
return self;
}
oily_png-1.2.1/ext/oily_png/resampling.h 0000664 0000000 0000000 00000001361 12766025717 0020303 0 ustar 00root root 0000000 0000000 #ifndef OILY_PNG_RESAMPLING_H
#define OILY_PNG_RESAMPLING_H
/*
* Generates the interpolation steps (nearest neighbour) through two values.
*/
void oily_png_generate_steps_residues(long width, long new_width, long *steps, long *residues);
/*
* Generates the interpolation steps through two values.
*
* Returns a Ruby Array
*/
VALUE oily_png_canvas_steps_residues(VALUE self, VALUE width, VALUE new_width);
VALUE oily_png_canvas_steps(VALUE self, VALUE width, VALUE new_width);
/*
* Performs nearest neighbor interpolation on the Canvas
*/
VALUE oily_png_canvas_resample_nearest_neighbor_bang(VALUE self, VALUE new_width, VALUE new_height);
VALUE oily_png_canvas_resample_bilinear_bang(VALUE self, VALUE new_width, VALUE new_height);
#endif
oily_png-1.2.1/lib/ 0000775 0000000 0000000 00000000000 12766025717 0014116 5 ustar 00root root 0000000 0000000 oily_png-1.2.1/lib/oily_png.rb 0000664 0000000 0000000 00000001016 12766025717 0016261 0 ustar 00root root 0000000 0000000 require 'chunky_png'
module OilyPNG
def self.included(base)
base::Canvas.send(:extend, OilyPNG::PNGDecoding)
base::Canvas.send(:include, OilyPNG::PNGEncoding)
base::Color.send(:include, OilyPNG::Color)
base::Color.extend OilyPNG::Color
base::Canvas.send(:include, OilyPNG::Resampling)
base::Canvas.send(:include, OilyPNG::Operations)
end
end
require 'oily_png/version'
require 'oily_png/oily_png'
require 'oily_png/canvas'
# Include mixin into ChunkyPNG
ChunkyPNG.send(:include, OilyPNG)
oily_png-1.2.1/lib/oily_png/ 0000775 0000000 0000000 00000000000 12766025717 0015736 5 ustar 00root root 0000000 0000000 oily_png-1.2.1/lib/oily_png/canvas.rb 0000664 0000000 0000000 00000000426 12766025717 0017540 0 ustar 00root root 0000000 0000000 require 'chunky_png'
require 'oily_png/oily_png'
module OilyPNG
class Canvas < ChunkyPNG::Canvas
extend OilyPNG::PNGDecoding
include OilyPNG::PNGEncoding
include OilyPNG::Operations
include OilyPNG::Resampling
end
module Color
extend self
end
end oily_png-1.2.1/lib/oily_png/version.rb 0000664 0000000 0000000 00000000047 12766025717 0017751 0 ustar 00root root 0000000 0000000 module OilyPNG
VERSION = "1.2.1"
end
oily_png-1.2.1/oily_png.gemspec 0000664 0000000 0000000 00000002356 12766025717 0016543 0 ustar 00root root 0000000 0000000 # -*- encoding: utf-8 -*-
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'oily_png/version'
Gem::Specification.new do |s|
s.name = 'oily_png'
s.rubyforge_project = s.name
# Do not change the version and date fields by hand. This will be done
# automatically by the gem release script.
s.version = OilyPNG::VERSION
s.summary = "Native mixin to speed up ChunkyPNG"
s.description = <<-EOT
This Ruby C extenstion defines a module that can be included into ChunkyPNG to improve its speed.
EOT
s.license = 'MIT'
s.authors = ['Willem van Bergen']
s.email = ['willem@railsdoctors.com']
s.homepage = 'http://wiki.github.com/wvanbergen/oily_png'
s.extensions = ["ext/oily_png/extconf.rb"]
s.require_paths = ["lib", "ext"]
s.add_runtime_dependency('chunky_png', '~> 1.3.7')
s.add_development_dependency('rake')
s.add_development_dependency('rake-compiler')
s.add_development_dependency('rspec', '~> 3')
s.rdoc_options << '--title' << s.name << '--main' << 'README.rdoc' << '--line-numbers' << '--inline-source'
s.extra_rdoc_files = ['README.rdoc']
s.files = `git ls-files`.split($/)
s.test_files = s.files.grep(%r{^(test|spec|features)/})
end
oily_png-1.2.1/spec/ 0000775 0000000 0000000 00000000000 12766025717 0014302 5 ustar 00root root 0000000 0000000 oily_png-1.2.1/spec/color_spec.rb 0000664 0000000 0000000 00000003063 12766025717 0016761 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe OilyPNG::Color do
include OilyPNG::Color
before(:each) do
@white = 0xffffffff
@black = 0x000000ff
@opaque = 0x0a6496ff
@non_opaque = 0x0a649664
@fully_transparent = 0x0a649600
end
describe '#compose_quick' do
it 'should use the foregorund color as is when the background color is fully transparent' do
expect(compose_quick(@non_opaque, @fully_transparent)).to be(@non_opaque)
end
it 'should use the foregorund color as is when an opaque color is given as foreground color' do
expect(compose_quick(@opaque, @white)).to be(@opaque)
end
it 'should use the background color as is when a fully transparent pixel is given as foreground color' do
expect(compose_quick(@fully_transparent, @white)).to be(@white)
end
it 'should compose pixels correctly' do
expect(compose_quick(@non_opaque, @white)).to be(0x9fc2d6ff)
end
it 'should compose colors exactly the same as ChunkyPNG' do
fg, bg = rand(0xffffffff), rand(0xffffffff)
expect(compose_quick(fg, bg)).to be(ChunkyPNG::Color.compose_quick(fg, bg))
end
end
describe '#euclidean_distance_rgba' do
let(:color_a) { rand(0xffffffff) }
let(:color_b) { rand(0xffffffff) }
subject { euclidean_distance_rgba(color_a, color_b) }
it { is_expected.to eq(ChunkyPNG::Color.euclidean_distance_rgba(color_a, color_b)) }
context 'when both colors are the same' do
let(:color_b) { color_a }
it { is_expected.to eq(0) }
end
end
end
oily_png-1.2.1/spec/decoding_spec.rb 0000664 0000000 0000000 00000004332 12766025717 0017417 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe OilyPNG::PNGDecoding do
it "should call ChunkyPNG::Color.pixel_bytesize in the pure ruby version" do
expect(ChunkyPNG::Color).to receive(:pixel_bytesize).and_return(3)
ChunkyPNG::Canvas.from_file(resource_file('square.png'))
end
it "should not call ChunkyPNG::Color.pixel_bytesize in the native version" do
expect(ChunkyPNG::Color).to receive(:pixel_bytesize).never
OilyPNG::Canvas.from_file(resource_file('square.png'))
end
context 'decoding different filtering methods' do
before(:all) { @reference = ChunkyPNG::Canvas.from_file(resource_file('nonsquare.png'))}
it "should decode NONE filtering exactly the same as ChunkyPNG" do
filtered_data = @reference.to_blob(:filtering => ChunkyPNG::FILTER_NONE)
expect(ChunkyPNG::Canvas.from_blob(filtered_data)).to eq(OilyPNG::Canvas.from_blob(filtered_data))
end
it "should decode SUB filtering exactly the same as ChunkyPNG" do
filtered_data = @reference.to_blob(:filtering => ChunkyPNG::FILTER_SUB)
expect(ChunkyPNG::Canvas.from_blob(filtered_data)).to eq(OilyPNG::Canvas.from_blob(filtered_data))
end
it "should decode UP filtering exactly the same as ChunkyPNG" do
filtered_data = @reference.to_blob(:filtering => ChunkyPNG::FILTER_UP)
expect(ChunkyPNG::Canvas.from_blob(filtered_data)).to eq(OilyPNG::Canvas.from_blob(filtered_data))
end
it "should decode AVERAGE filtering exactly the same as ChunkyPNG" do
filtered_data = @reference.to_blob(:filtering => ChunkyPNG::FILTER_AVERAGE)
expect(ChunkyPNG::Canvas.from_blob(filtered_data)).to eq(OilyPNG::Canvas.from_blob(filtered_data))
end
it "should decode PAETH filtering exactly the same as ChunkyPNG" do
filtered_data = @reference.to_blob(:filtering => ChunkyPNG::FILTER_PAETH)
expect(ChunkyPNG::Canvas.from_blob(filtered_data)).to eq(OilyPNG::Canvas.from_blob(filtered_data))
end
end
context 'decoding compatibility with ChunkyPNG' do
resource_files.each do |file|
it "should #{File.basename(file)} the same as ChunkyPNG" do
expect(OilyPNG::Canvas.from_file(file).pixels).to eq(ChunkyPNG::Canvas.from_file(file).pixels)
end
end
end
end
oily_png-1.2.1/spec/encoding_spec.rb 0000664 0000000 0000000 00000020266 12766025717 0017435 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe OilyPNG::PNGEncoding do
context 'encoding different color settings without palette' do
before do
@canvas = ChunkyPNG::Canvas.from_file(resource_file('gray.png'))
@oily_canvas = OilyPNG::Canvas.from_canvas(@canvas)
end
it "should encode an image using 8-bit grayscale correctly" do
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_GRAYSCALE, 8, ChunkyPNG::FILTER_NONE)
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_GRAYSCALE, 8, ChunkyPNG::FILTER_NONE)
expect(stream1).to eq(stream2)
end
it "should encode an image using 4-bit grayscale correctly" do
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_GRAYSCALE, 4, ChunkyPNG::FILTER_NONE)
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_GRAYSCALE, 4, ChunkyPNG::FILTER_NONE)
expect(stream1).to eq(stream2)
end
it "should encode an image using 2-bit grayscale correctly" do
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_GRAYSCALE, 2, ChunkyPNG::FILTER_NONE)
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_GRAYSCALE, 2, ChunkyPNG::FILTER_NONE)
expect(stream1).to eq(stream2)
end
it "should encode an image using 1-bit grayscale correctly" do
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_GRAYSCALE, 1, ChunkyPNG::FILTER_NONE)
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_GRAYSCALE, 1, ChunkyPNG::FILTER_NONE)
expect(stream1).to eq(stream2)
end
it "should encode an image using 8-bit grayscale alpha correctly" do
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_GRAYSCALE_ALPHA, 8, ChunkyPNG::FILTER_NONE)
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_GRAYSCALE_ALPHA, 8, ChunkyPNG::FILTER_NONE)
expect(stream1).to eq(stream2)
end
it "should encode an image using 8-bit truecolor correctly" do
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR, 8, ChunkyPNG::FILTER_NONE)
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR, 8, ChunkyPNG::FILTER_NONE)
expect(stream1).to eq(stream2)
end
it "should encode an image using 8-bit truecolor alpha correctly" do
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR_ALPHA, 8, ChunkyPNG::FILTER_NONE)
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR_ALPHA, 8, ChunkyPNG::FILTER_NONE)
expect(stream1).to eq(stream2)
end
end
context 'encoding with paletted images using different bitrates' do
before do
@canvas = ChunkyPNG::Canvas.from_file(resource_file('gray.png'))
@oily_canvas = OilyPNG::Canvas.from_canvas(@canvas)
@canvas.encoding_palette = @canvas.palette
@canvas.encoding_palette.to_plte_chunk
@oily_canvas.encoding_palette = @oily_canvas.palette
@oily_canvas.encoding_palette.to_plte_chunk
end
it "should encode an image using 8-bit indexed colors correctly" do
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_INDEXED, 8, ChunkyPNG::FILTER_NONE)
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_INDEXED, 8, ChunkyPNG::FILTER_NONE)
expect(stream1).to eq(stream2)
end
it "should encode an image using 4-bit indexed colors correctly" do
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_INDEXED, 4, ChunkyPNG::FILTER_NONE)
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_INDEXED, 4, ChunkyPNG::FILTER_NONE)
expect(stream1).to eq(stream2)
end
it "should encode an image using 2-bit indexed colors correctly" do
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_INDEXED, 2, ChunkyPNG::FILTER_NONE)
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_INDEXED, 2, ChunkyPNG::FILTER_NONE)
expect(stream1).to eq(stream2)
end
it "should encode an image using 1-bit indexed colors correctly" do
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_INDEXED, 1, ChunkyPNG::FILTER_NONE)
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_INDEXED, 1, ChunkyPNG::FILTER_NONE)
expect(stream1).to eq(stream2)
end
end
context 'encoding different filters' do
before do
@canvas = ChunkyPNG::Canvas.from_file(resource_file('nonsquare.png'))
@oily_canvas = OilyPNG::Canvas.from_canvas(@canvas)
end
it "should encode correctly with no filtering" do
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR, 8, ChunkyPNG::FILTER_NONE)
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR, 8, ChunkyPNG::FILTER_NONE)
expect(stream1).to eq(stream2)
end
it "should encode correctly with sub filtering" do
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR, 8, ChunkyPNG::FILTER_SUB)
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR, 8, ChunkyPNG::FILTER_SUB)
expect(stream1).to eq(stream2)
end
it "should encode correctly with up filtering" do
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR, 8, ChunkyPNG::FILTER_UP)
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR, 8, ChunkyPNG::FILTER_UP)
expect(stream1).to eq(stream2)
end
it "should encode correctly with average filtering" do
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR, 8, ChunkyPNG::FILTER_AVERAGE)
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR, 8, ChunkyPNG::FILTER_AVERAGE)
expect(stream1).to eq(stream2)
end
it "should encode correctly with paeth filtering" do
@oily_canvas.send(:encode_png_image_pass_to_stream, stream1 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR, 8, ChunkyPNG::FILTER_PAETH)
@canvas.send(:encode_png_image_pass_to_stream, stream2 = ChunkyPNG::Datastream.empty_bytearray, ChunkyPNG::COLOR_TRUECOLOR, 8, ChunkyPNG::FILTER_PAETH)
expect(stream1).to eq(stream2)
end
end
it "should encode an interlaced image correctly" do
canvas = ChunkyPNG::Canvas.from_file(resource_file('interlaced.png'))
data = OilyPNG::Canvas.from_canvas(canvas).to_blob(:interlace => true)
ds = ChunkyPNG::Datastream.from_blob(data)
expect(ds.header_chunk.interlace).to eq(ChunkyPNG::INTERLACING_ADAM7)
expect(ChunkyPNG::Canvas.from_datastream(ds)).to eq(canvas)
end
end
oily_png-1.2.1/spec/operations_spec.rb 0000664 0000000 0000000 00000010546 12766025717 0020032 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe OilyPNG::Operations do
describe '#compose!' do
subject { oily_reference_canvas('operations') }
it "should compose two images without offset exactly the same way as ChunkyPNG" do
subcanvas = ChunkyPNG::Canvas.new(4, 8, ChunkyPNG::Color.rgba(0, 0, 0, 75))
subject.compose!(subcanvas)
expect(ChunkyPNG::Canvas.from_canvas(subject)).to eq(reference_canvas('operations').compose(subcanvas))
end
it "should compose two images with offset exactly the same way as ChunkyPNG" do
subject.compose!(ChunkyPNG::Canvas.new(4, 8, ChunkyPNG::Color.rgba(0, 0, 0, 75)), 8, 4)
expect(subject).to eql(oily_reference_canvas('composited'))
end
it "should return itself" do
expect(subject.compose!(OilyPNG::Canvas.new(1,1))).to be(subject)
end
it "should raise an exception when the pixels to compose fall outside the image" do
# For now this raises a runtime error, but it should probably raise a ChunkyPNG::OutOfBounds error
expect { subject.compose!(OilyPNG::Canvas.new(1,1), 16, 16) }.to raise_error
end
end
describe '#replace!' do
subject { oily_reference_canvas('operations') }
it "should compose two images without offset exactly the same way as ChunkyPNG" do
subcanvas = ChunkyPNG::Canvas.new(3, 2, ChunkyPNG::Color.rgb(200, 255, 0))
subject.replace!(subcanvas)
expect(ChunkyPNG::Canvas.from_canvas(subject)).to eq(reference_canvas('operations').replace(subcanvas))
end
it "should compose two images with offset exactly the same way as ChunkyPNG" do
subject.replace!(ChunkyPNG::Canvas.new(3, 2, ChunkyPNG::Color.rgb(200, 255, 0)), 5, 4)
expect(subject).to eq(oily_reference_canvas('replaced'))
end
it "should return itself" do
expect(subject.replace!(OilyPNG::Canvas.new(1,1))).to be(subject)
end
it "should raise an exception when the pixels to compose fall outside the image" do
# For now this raises a runtime error, but it should probably raise a ChunkyPNG::OutOfBounds error
expect { subject.replace!(OilyPNG::Canvas.new(1,1), 16, 16) }.to raise_error
end
end
describe '#rotate_left!' do
subject { OilyPNG::Canvas.new(2, 3, [1, 2, 3, 4, 5, 6]) }
it "should rotate the pixels 90 degrees clockwise" do
subject.rotate_left!
expect(subject).to eql OilyPNG::Canvas.new(3, 2, [2, 4, 6, 1, 3, 5] )
end
it "should return itself" do
expect(subject.rotate_left!).to equal(subject)
end
it "should change the image dimensions" do
expect { subject.rotate_left! }.to change { subject.dimension }.
from(ChunkyPNG::Dimension('2x3')).to(ChunkyPNG::Dimension('3x2'))
end
it "it should rotate 180 degrees when applied twice" do
subject_dup = subject.dup
expect(subject.rotate_left!.rotate_left!).to eql subject_dup.rotate_180
end
it "it should rotate right when applied three times" do
subject_dup = subject.dup
expect(subject.rotate_left!.rotate_left!.rotate_left!).to eql subject_dup.rotate_right!
end
it "should return itself when applied four times" do
subject_dup = subject.dup
expect(subject.rotate_left!.rotate_left!.rotate_left!.rotate_left!).to eql subject_dup
end
end
describe '#rotate_right!' do
subject { OilyPNG::Canvas.new(2, 3, [1, 2, 3, 4, 5, 6]) }
it "should rotate the pixels 90 degrees clockwise" do
subject.rotate_right!
expect(subject).to eql OilyPNG::Canvas.new(3, 2, [5, 3, 1, 6, 4, 2] )
end
it "should return itself" do
expect(subject.rotate_right!).to equal(subject)
end
it "should change the image dimensions" do
expect { subject.rotate_right! }.to change { subject.dimension }.
from(ChunkyPNG::Dimension('2x3')).to(ChunkyPNG::Dimension('3x2'))
end
it "it should rotate 180 degrees when applied twice" do
subject_dup = subject.dup
expect(subject.rotate_right!.rotate_right!).to eql subject_dup.rotate_180
end
it "it should rotate left when applied three times" do
subject_dup = subject.dup
expect(subject.rotate_right!.rotate_right!.rotate_right!).to eql subject_dup.rotate_left
end
it "should return itself when applied four times" do
subject_dup = subject.dup
expect(subject.rotate_right!.rotate_right!.rotate_right!.rotate_right!).to eql subject_dup
end
end
end
oily_png-1.2.1/spec/resampling_spec.rb 0000664 0000000 0000000 00000004727 12766025717 0020014 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe OilyPNG::Resampling do
include OilyPNG::Resampling
describe '#steps' do
it "should generate the steps from 4 to 8 as [0,0,1,1,2,2,3,3]" do
expect(steps(4, 8)).to eq([0, 0, 1, 1, 2, 2, 3, 3])
end
it "should generate the steps the same as ChunkyPNG" do
image = ChunkyPNG::Image.new(1,1)
expect(steps( 2, 8)).to eq(image.send(:steps, 2, 8))
expect(steps( 2, 11)).to eq(image.send(:steps, 2,11))
expect(steps(11, 5)).to eq(image.send(:steps,11, 5))
end
end
describe '#resample_nearest_neighbor!' do
before(:all) { @reference = ChunkyPNG::Canvas.from_file(resource_file('nonsquare.png'))}
it "should resample [0,1,2,3] to 4x4 properly" do
expect(OilyPNG::Canvas.new(2,2,[0,1,2,3]).resample_nearest_neighbor(4,4)).to eq(OilyPNG::Canvas.new(4,4,[0,0,1,1,0,0,1,1,2,2,3,3,2,2,3,3]))
end
it "should resample [0,1,2,3] to 99x45 as ChunkyPNG does" do
expect(ChunkyPNG::Canvas.new(2,2,[0,1,2,3]).resample_nearest_neighbor(99,45)).to eq(OilyPNG::Canvas.new(2,2,[0,1,2,3]).resample_nearest_neighbor(99,45))
end
it "should resample an image to 10x20 as ChunkyPNG does" do
expect(@reference.resample_nearest_neighbor(10,20)).to eq(OilyPNG::Canvas.from_canvas(@reference).resample_nearest_neighbor(10,20))
end
it "should resample an image to 11x19 as ChunkyPNG does" do
expect(@reference.resample_nearest_neighbor(11,19)).to eq(OilyPNG::Canvas.from_canvas(@reference).resample_nearest_neighbor(11,19))
end
end
describe '#resample_bilinear!' do
before(:all) { @reference = ChunkyPNG::Canvas.from_file(resource_file('nonsquare.png'))}
it "should resample an image to 10x20 as ChunkyPNG does" do
expect(@reference.resample_bilinear(10,20)).to eq(OilyPNG::Canvas.from_canvas(@reference).resample_bilinear(10,20))
end
it "should resample an image to 11x19 as ChunkyPNG does" do
expect(@reference.resample_bilinear(11,19)).to eq(OilyPNG::Canvas.from_canvas(@reference).resample_bilinear(11,19))
end
it "should upsample an image to 88x44 as ChunkyPNG does" do
expect(@reference.resample_bilinear(88,44)).to eq(OilyPNG::Canvas.from_canvas(@reference).resample_bilinear(88,44))
end
it "should not crash upsampling tall image" do
@reference = ChunkyPNG::Canvas.from_file(resource_file('nonsquaretall.png'))
expect { OilyPNG::Canvas.from_canvas(@reference).resample_bilinear(44,88) }.to_not raise_error
end
end
end
oily_png-1.2.1/spec/resources/ 0000775 0000000 0000000 00000000000 12766025717 0016314 5 ustar 00root root 0000000 0000000 oily_png-1.2.1/spec/resources/basi0g01.png 0000775 0000000 0000000 00000000331 12766025717 0020330 0 ustar 00root root 0000000 0000000 PNG
IHDR ,w gAMA 1_ IDATx-10EƂz .z'V9 cX,e5|KxOp笹pi\Yc*L'Dd[6癹+MKOKzҍ2-czpEHp-/zQ!塌 Qf" IENDB` oily_png-1.2.1/spec/resources/basi0g02.png 0000775 0000000 0000000 00000000232 12766025717 0020331 0 ustar 00root root 0000000 0000000 PNG
IHDR k
gAMA 1_ QIDATxcPb` r`p16nDgg]! dDCH dJHR?0A
9 RM} IENDB` oily_png-1.2.1/spec/resources/basi0g04.png 0000775 0000000 0000000 00000000367 12766025717 0020344 0 ustar 00root root 0000000 0000000 PNG
IHDR gAMA 1_ IDATxeQ0D:(u AAAAA$TI~ò¶EuEĺCsGjw<#
^bs8Al.iGZ'(CYd:"k@i2Gpr:1(Kkce s{ig 826N'M IENDB` oily_png-1.2.1/spec/resources/basi0g08.png 0000775 0000000 0000000 00000000376 12766025717 0020350 0 ustar 00root root 0000000 0000000 PNG
IHDR ! gAMA 1_ IDATx]
0B{C%>x!Kܦ$]!2,$UIBH"*V$$ŏ J l, lܲ 9:eSW}q@Şp쿣;_|?/