bugzilla.
http://rt.cpan.org/Ticket/Display.html?id=32926
+ - writing a 2 or 4 channel image to a JPEG will now write that image as
+ if composited against a background, black by default, overridable
+ with the i_background tag/parameter.
+ https://rt.cpan.org/Ticket/Display.html?id=29876
+
Bug fixes:
- Imager::Matrix2d->translate() now only requires one of the x or y
gif_loop_count => 'gif_loop',
);
+# options that should be converted to colors
+my %color_opts = map { $_ => 1 } qw/i_background/;
+
sub _set_opts {
my ($self, $opts, $prefix, @imgs) = @_;
}
next unless $tagname =~ /^\Q$prefix/;
my $value = $opts->{$opt};
+ if ($color_opts{$opt}) {
+ $value = _color($value);
+ unless ($value) {
+ $self->_set_error($Imager::ERRSTR);
+ return;
+ }
+ }
if (ref $value) {
if (UNIVERSAL::isa($value, "Imager::Color")) {
my $tag = sprintf("color(%d,%d,%d,%d)", $value->rgba);
return 0;
}
+/*
+=item i_get_file_background(im, &bg)
+
+Retrieve the file write background color tag from the image.
+
+If not present, returns black.
+
+=cut
+*/
+
+void
+i_get_file_background(i_img *im, i_color *bg) {
+ if (!i_tags_get_color(&im->tags, "i_background", 0, bg)) {
+ /* black default */
+ bg->channel[0] = bg->channel[1] = bg->channel[2] = 0;
+ }
+ /* always full alpha */
+ bg->channel[3] = 255;
+}
+
/*
=back
extern i_img *i_img_double_new(int x, int y, int ch);
extern int i_img_is_monochrome(i_img *im, int *zero_is_white);
+extern void i_get_file_background(i_img *im, i_color *bg);
const char * i_test_format_probe(io_glue *data, int length);
int got_xres, got_yres, aspect_only, resunit;
double xres, yres;
int comment_entry;
+ int want_channels = im->channels;
struct jpeg_compress_struct cinfo;
struct my_error_mgr jerr;
JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
int row_stride; /* physical row width in image buffer */
unsigned char * data = NULL;
+ i_color *line_buf = NULL;
mm_log((1,"i_writejpeg(im %p, ig %p, qfactor %d)\n", im, ig, qfactor));
io_glue_commit_types(ig);
if (!(im->channels==1 || im->channels==3)) {
- i_push_error(0, "only 1 or 3 channels images can be saved as JPEG");
- return 0;
+ want_channels = im->channels - 1;
}
quality = qfactor;
jpeg_destroy_compress(&cinfo);
if (data)
myfree(data);
+ if (line_buf)
+ myfree(line_buf);
return 0;
}
cinfo.image_width = im -> xsize; /* image width and height, in pixels */
cinfo.image_height = im -> ysize;
- if (im->channels==3) {
+ if (want_channels==3) {
cinfo.input_components = 3; /* # of color components per pixel */
cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
}
- if (im->channels==1) {
+ if (want_channels==1) {
cinfo.input_components = 1; /* # of color components per pixel */
cinfo.in_color_space = JCS_GRAYSCALE; /* colorspace of input image */
}
row_stride = im->xsize * im->channels; /* JSAMPLEs per row in image_buffer */
- if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits) {
+ if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits
+ && im->channels == want_channels) {
image_buffer=im->idata;
while (cinfo.next_scanline < cinfo.image_height) {
(void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
}
- else {
+ else if (im->channels == want_channels) {
data = mymalloc(im->xsize * im->channels);
if (data) {
while (cinfo.next_scanline < cinfo.image_height) {
return 0; /* out of memory? */
}
}
+ else {
+ i_color bg;
+ int x;
+ int ch;
+ i_color const *linep;
+ unsigned char * datap;
+
+ line_buf = mymalloc(sizeof(i_color) * im->xsize);
+
+ i_get_file_background(im, &bg);
+
+ data = mymalloc(im->xsize * want_channels);
+ while (cinfo.next_scanline < cinfo.image_height) {
+ i_glin(im, 0, im->xsize, cinfo.next_scanline, line_buf);
+ i_adapt_colors_bg(want_channels, im->channels, line_buf, im->xsize, &bg);
+ datap = data;
+ linep = line_buf;
+ for (x = 0; x < im->xsize; ++x) {
+ for (ch = 0; ch < want_channels; ++ch) {
+ *datap++ = linep->channel[ch];
+ }
+ ++linep;
+ }
+ row_pointer[0] = data;
+ (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
+ }
+ myfree(line_buf);
+ myfree(data);
+ }
/* Step 6: Finish compression */
=head2 JPEG
You can supply a C<jpegquality> parameter (0-100) when writing a JPEG
-file, which defaults to 75%. Only 1 and 3 channel images
-can be written, including 1 and 3 channel paletted images.
+file, which defaults to 75%. If you write an image with an alpha
+channel to a jpeg file then it will be composited against the
+background set by the C<i_background> parameter (or tag).
$img->write(file=>'foo.jpg', jpegquality=>90) or die $img->errstr;
X<i_format tag>X<tags, i_format>i_format - The file format this file
was read from.
+=item *
+
+X<i_background>X<tags, i_background>i_background - used when writing
+an image with an alpha channel to a file format that doesn't support
+alpha channels. The C<write> method will convert a normal color
+specification like "#FF0000" into a color object for you, but if you
+set this as a tag you will need to format it like
+C<color(>I<red>C<,>I<green>C<,>I<blue>C<)>, eg color(255,0,0).
+
=back
=head2 Quantization options
@ISA = qw(Exporter);
@EXPORT_OK = qw(diff_text_with_nul
test_image_raw test_image_16 test_image test_image_double
- is_color3 is_color1 is_color4
+ is_color3 is_color1 is_color4 is_color_close3
is_fcolor4
is_image is_image_similar
image_bounds_checks mask_tests
return 1;
}
+sub is_color_close3($$$$$$) {
+ my ($color, $red, $green, $blue, $tolerance, $comment) = @_;
+
+ my $builder = Test::Builder->new;
+
+ unless (defined $color) {
+ $builder->ok(0, $comment);
+ $builder->diag("color is undef");
+ return;
+ }
+ unless ($color->can('rgba')) {
+ $builder->ok(0, $comment);
+ $builder->diag("color is not a color object");
+ return;
+ }
+
+ my ($cr, $cg, $cb) = $color->rgba;
+ unless ($builder->ok(abs($cr - $red) <= $tolerance
+ && abs($cg - $green) <= $tolerance
+ && abs($cb - $blue) <= $tolerance, $comment)) {
+ $builder->diag(<<END_DIAG);
+Color out of tolerance ($tolerance):
+ Red: expected $red vs received $cr
+Green: expected $green vs received $cg
+ Blue: expected $blue vs received $cb
+END_DIAG
+ return;
+ }
+
+ return 1;
+}
+
sub is_color4($$$$$$) {
my ($color, $red, $green, $blue, $alpha, $comment) = @_;
}
}
+void
+#ifdef IM_EIGHT_BIT
+i_adapt_colors_bg
+#else
+i_adapt_fcolors_bg
+#endif
+(int out_channels, int in_channels, IM_COLOR *colors,
+ size_t count, IM_COLOR const *bg) {
+ if (out_channels == in_channels)
+ return;
+ if (count == 0)
+ return;
+
+ switch (out_channels) {
+ case 2:
+ case 4:
+ IM_ADAPT_COLORS(out_channels, in_channels, colors, count);
+ return;
+
+ case 1:
+ switch (in_channels) {
+ case 3:
+ IM_ADAPT_COLORS(out_channels, in_channels, colors, count);
+ return;
+
+ case 2:
+ {
+ /* apply alpha against our given background */
+ IM_WORK_T grey_bg = IM_ROUND(color_to_grey(bg));
+ while (count) {
+ colors->channel[0] =
+ (colors->channel[0] * colors->channel[1] +
+ grey_bg * (IM_SAMPLE_MAX - colors->channel[1])) / IM_SAMPLE_MAX;
+ ++colors;
+ --count;
+ }
+ }
+ break;
+
+ case 4:
+ {
+ IM_WORK_T grey_bg = IM_ROUND(color_to_grey(bg));
+ while (count) {
+ IM_WORK_T src_grey = IM_ROUND(color_to_grey(colors));
+ colors->channel[0] =
+ (src_grey * colors->channel[3]
+ + grey_bg * (IM_SAMPLE_MAX - colors->channel[3])) / IM_SAMPLE_MAX;
+ ++colors;
+ --count;
+ }
+ }
+ break;
+ }
+ break;
+
+ case 3:
+ switch (in_channels) {
+ case 1:
+ IM_ADAPT_COLORS(out_channels, in_channels, colors, count);
+ return;
+
+ case 2:
+ {
+ while (count) {
+ int ch;
+ IM_WORK_T src_grey = colors->channel[0];
+ IM_WORK_T src_alpha = colors->channel[1];
+ for (ch = 0; ch < 3; ++ch) {
+ colors->channel[ch] =
+ (src_grey * src_alpha
+ + bg->channel[ch] * (IM_SAMPLE_MAX - src_alpha))
+ / IM_SAMPLE_MAX;
+ }
+ ++colors;
+ --count;
+ }
+ }
+ break;
+
+ case 4:
+ {
+ while (count) {
+ int ch;
+ IM_WORK_T src_alpha = colors->channel[3];
+ for (ch = 0; ch < 3; ++ch) {
+ colors->channel[ch] =
+ (colors->channel[ch] * src_alpha
+ + bg->channel[ch] * (IM_SAMPLE_MAX - src_alpha))
+ / IM_SAMPLE_MAX;
+ }
+ ++colors;
+ --count;
+ }
+ }
+ break;
+ }
+ break;
+ }
+
+}
+
#/code
#!perl -w
use strict;
use Imager qw(:all);
-use Test::More tests => 88;
+use Test::More tests => 94;
+use Imager::Test qw(is_color_close3);
init_log("testout/t101jpeg.log",1);
cmp_ok($im->errstr, '=~', qr/format 'jpeg' not supported/, "check no jpeg message");
ok(!grep($_ eq 'jpeg', Imager->read_types), "check jpeg not in read types");
ok(!grep($_ eq 'jpeg', Imager->write_types), "check jpeg not in write types");
- skip("no jpeg support", 82);
+ skip("no jpeg support", 88);
}
} else {
open(FH,">testout/t101.jpg") || die "cannot open testout/t101.jpg for writing\n";
# attempting to write a 4 channel image to a bufchain would
# cause a seg fault.
# it should fail still
- my $im = Imager->new(xsize => 10, ysize => 10, channels => 4);
+ # overridden by # 29876
+ # give 4/2 channel images a background color when saving to JPEG
+ my $im = Imager->new(xsize => 16, ysize => 16, channels => 4);
+ $im->box(filled => 1, xmin => 8, color => '#FFE0C0');
my $data;
- ok(!$im->write(data => \$data, type => 'jpeg'),
- "should fail to write but shouldn't crash");
- is($im->errstr, "only 1 or 3 channels images can be saved as JPEG",
- "check the error message");
+ ok($im->write(data => \$data, type => 'jpeg'),
+ "should write with a black background");
+ my $imread = Imager->new;
+ ok($imread->read(data => $data, type => 'jpeg'), 'read it back');
+ is_color_close3($imread->getpixel('x' => 0, 'y' => 0), 0, 0, 0, 4,
+ "check it's black");
+ is_color_close3($imread->getpixel('x' => 15, 'y' => 9), 255, 224, 192, 4,
+ "check filled area filled");
+
+ # write with a red background
+ $data = '';
+ ok($im->write(data => \$data, type => 'jpeg', i_background => '#FF0000'),
+ "write with red background");
+ ok($imread->read(data => $data, type => 'jpeg'), "read it back");
+ is_color_close3($imread->getpixel('x' => 0, 'y' => 0), 255, 0, 0, 4,
+ "check it's red");
+ is_color_close3($imread->getpixel('x' => 15, 'y' => 9), 255, 224, 192, 4,
+ "check filled area filled");
}
SKIP:
{ # Issue # 18496