implementations of mixing scaling.
modified imtoc.perl to allow non-conditional #code sections to allow
creation ofr 8 and double/sample versions of support functions.
fixed a bug in an optimization that avoids vertically scaling when the
vertical size stays the same.
The change from double/sample only to both saved about 20% on
scalebench time (which also loads/saves the images)
samples/samp-tags.html Form for samp-tags.cgi
samples/slant_text.pl Using $font->transform() to slant text
samples/tk-photo.pl
-scale.c Newer scaling code
+scale.im Newer scaling code
spot.perl For making an ordered dither matrix from a spot function
stackmach.c
stackmach.h
my @allfiles = (@ARGV) x 120;
my $srcdir = '.';
-my %opts1 = (scalefactor=>.333334);
-my %opts2 = (scalefactor=>.25);
+my %opts1 = (scalefactor=>.333334, qtype => 'mixing');
+my %opts2 = (scalefactor=>.25, qtype => 'mixing');
my %exopts=();
for my $file (@allfiles) {
# print STDERR "reading $srcdir/$file\n";
push @out, "#line 1 \"$src\"\n";
while (defined(my $line = <SRC>)) {
- if ($line =~ /^\#code (.+)$/) {
+ if ($line =~ /^\#code\s+(\S.+)$/) {
$save_code
and do { warn "$src:$code_line:Unclosed #code block\n"; ++$failed; };
$code_line = $. + 1;
$save_code = 1;
}
+ elsif ($line =~ /^\#code\s*$/) {
+ $save_code
+ and do { warn "$src:$code_line:Unclosed #code block\n"; ++$failed; };
+
+ $cond = '';
+ $cond_line = 0;
+ $code_line = $. + 1;
+ $save_code = 1;
+ }
elsif ($line =~ /^\#\/code$/) {
$save_code
or do { warn "$src:$.:#/code without #code\n"; ++$failed; next; };
- push @out, "#line $cond_line \"$src\"\n";
- push @out, " if ($cond) {\n";
+ if ($cond) {
+ push @out, "#line $cond_line \"$src\"\n";
+ push @out, " if ($cond) {\n";
+ }
+ push @out, "#undef IM_EIGHT_BIT\n",
+ "#define IM_EIGHT_BIT 1\n";
push @out, "#line $code_line \"$src\"\n";
push @out, byte_samples(@code);
- push @out, " }\n", " else {\n";
+ push @out, " }\n", " else {\n"
+ if $cond;
+ push @out, "#undef IM_EIGHT_BIT\n";
push @out, "#line $code_line \"$src\"\n";
push @out, double_samples(@code);
- push @out, " }\n";
+ push @out, " }\n"
+ if $cond;
push @out, "#line $. \"$src\"\n";
@code = ();
$save_code = 0;
s/\bIM_WORK_T\b/int/g;
s/\bIM_Sf\b/"%d"/g;
s/\bIM_Wf\b/"%d"/g;
+ s/\bIM_SUFFIX\((\w+)\)/$1_8/g;
}
@lines;
s/\bIM_WORK_T\b/double/g;
s/\bIM_Sf\b/"%f"/g;
s/\bIM_Wf\b/"%f"/g;
+ s/\bIM_SUFFIX\((\w+)\)/$1_double/g;
}
@lines;
where I<expression> should return true if processing should be done at
8-bits/sample.
+You can also use a #code block around a function definition to produce
+8-bit and double sample versions of a function. In this case #code
+has no expression and you will need to use IM_SUFFIX() to produce
+different function names.
+
The end of a sample-independent section of code is terminated by:
#/code
IM_Wf - format string for the work type, C<"%d"> or C<"%f">.
+=item *
+
+IM_SUFFIX(identifier) - adds _8 or _double onto the end of identifier.
+
+=item *
+
+IM_EIGHT_BIT - this is a macro defined only in 8-bit/sample code.
+
=back
Other types, functions and values may be added in the future.
$newimg = $img->copy();
- $newimg = $img->scale(xpixels=>400);
+ $newimg = $img->scale(xpixels=>400, qtype => 'mixing');
$newimg = $img->scale(xpixels=>400, ypixels=>400);
$newimg = $img->scale(xpixels=>400, ypixels=>400, type=>'min');
$newimg = $img->scale(scalefactor=>0.25);
+++ /dev/null
-#include "imager.h"
-
-/*
- * i_scale_mixing() is based on code contained in pnmscale.c, part of
- * the netpbm distribution. No code was copied from pnmscale but
- * the algorthm was and for this I thank the netpbm crew.
- *
- * Tony
- */
-
-/* pnmscale.c - read a portable anymap and scale it
-**
-** Copyright (C) 1989, 1991 by Jef Poskanzer.
-**
-** Permission to use, copy, modify, and distribute this software and its
-** documentation for any purpose and without fee is hereby granted, provided
-** that the above copyright notice appear in all copies and that both that
-** copyright notice and this permission notice appear in supporting
-** documentation. This software is provided "as is" without express or
-** implied warranty.
-**
-*/
-
-
-static void
-zero_row(i_fcolor *row, int width, int channels);
-static void
-accum_output_row(i_fcolor *accum, double fraction, i_fcolor const *in,
- int width, int channels);
-static void
-horizontal_scale(i_fcolor *out, int out_width,
- i_fcolor const *in, int in_width,
- int channels);
-
-/*
-=item i_scale_mixing
-
-Returns a new image scaled to the given size.
-
-Unlike i_scale_axis() this does a simple coverage of pixels from
-source to target and doesn't resample.
-
-Adapted from pnmscale.
-
-=cut
-*/
-i_img *
-i_scale_mixing(i_img *src, int x_out, int y_out) {
- i_img *result;
- i_fcolor *in_row = NULL;
- i_fcolor *xscale_row = NULL;
- i_fcolor *accum_row = NULL;
- int y;
- int in_row_bytes, out_row_bytes;
- double rowsleft, fracrowtofill;
- int rowsread;
- double y_scale;
-
- mm_log((1, "i_scale_mixing(src %p, x_out %d, y_out %d)\n",
- src, x_out, y_out));
-
- i_clear_error();
-
- if (x_out <= 0) {
- i_push_errorf(0, "output width %d invalid", x_out);
- return NULL;
- }
- if (y_out <= 0) {
- i_push_errorf(0, "output height %d invalid", y_out);
- return NULL;
- }
-
- in_row_bytes = sizeof(i_fcolor) * src->xsize;
- if (in_row_bytes / sizeof(i_fcolor) != src->xsize) {
- i_push_error(0, "integer overflow allocating input row buffer");
- return NULL;
- }
- out_row_bytes = sizeof(i_fcolor) * x_out;
- if (out_row_bytes / sizeof(i_fcolor) != x_out) {
- i_push_error(0, "integer overflow allocating output row buffer");
- return NULL;
- }
-
- if (x_out == src->xsize && y_out == src->ysize) {
- return i_copy(src);
- }
-
- y_scale = y_out / (double)src->ysize;
-
- result = i_sametype_chans(src, x_out, y_out, src->channels);
- if (!result)
- return NULL;
-
- in_row = mymalloc(in_row_bytes);
- accum_row = mymalloc(in_row_bytes);
- xscale_row = mymalloc(out_row_bytes);
-
- rowsread = 0;
- rowsleft = 0.0;
- for (y = 0; y < y_out; ++y) {
- if (y_out == src->ysize) {
- i_glinf(src, 0, src->xsize, y, accum_row);
- }
- else {
- fracrowtofill = 1.0;
- zero_row(accum_row, src->xsize, src->channels);
- while (fracrowtofill > 0) {
- if (rowsleft <= 0) {
- if (rowsread < src->ysize) {
- i_glinf(src, 0, src->xsize, rowsread, in_row);
- ++rowsread;
- }
- /* else just use the last row read */
-
- rowsleft = y_scale;
- }
- if (rowsleft < fracrowtofill) {
- accum_output_row(accum_row, rowsleft, in_row, src->xsize,
- src->channels);
- fracrowtofill -= rowsleft;
- rowsleft = 0;
- }
- else {
- accum_output_row(accum_row, fracrowtofill, in_row, src->xsize,
- src->channels);
- rowsleft -= fracrowtofill;
- fracrowtofill = 0;
- }
- }
- /* we've accumulated a vertically scaled row */
- if (x_out == src->xsize) {
- /* no need to scale */
- i_plinf(result, 0, x_out, y, accum_row);
- }
- else {
- horizontal_scale(xscale_row, x_out, accum_row, src->xsize,
- src->channels);
- i_plinf(result, 0, x_out, y, xscale_row);
- }
- }
- }
-
- myfree(in_row);
- myfree(accum_row);
- myfree(xscale_row);
-
- return result;
-}
-
-static void
-zero_row(i_fcolor *row, int width, int channels) {
- int x;
- int ch;
-
- /* with IEEE floats we could just use memset() but that's not
- safe in general under ANSI C */
- for (x = 0; x < width; ++x) {
- for (ch = 0; ch < channels; ++ch)
- row[x].channel[ch] = 0.0;
- }
-}
-
-static void
-accum_output_row(i_fcolor *accum, double fraction, i_fcolor const *in,
- int width, int channels) {
- int x, ch;
-
- for (x = 0; x < width; ++x) {
- for (ch = 0; ch < channels; ++ch) {
- accum[x].channel[ch] += in[x].channel[ch] * fraction;
- }
- }
-}
-
-static void
-horizontal_scale(i_fcolor *out, int out_width,
- i_fcolor const *in, int in_width,
- int channels) {
- double frac_col_to_fill, frac_col_left;
- int in_x;
- int out_x;
- double x_scale = (double)out_width / in_width;
- int ch;
- double accum[MAXCHANNELS] = { 0 };
-
- frac_col_to_fill = 1.0;
- out_x = 0;
- for (in_x = 0; in_x < in_width; ++in_x) {
- frac_col_left = x_scale;
- while (frac_col_left >= frac_col_to_fill) {
- for (ch = 0; ch < channels; ++ch)
- accum[ch] += frac_col_to_fill * in[in_x].channel[ch];
-
- for (ch = 0; ch < channels; ++ch) {
- out[out_x].channel[ch] = accum[ch];
- accum[ch] = 0;
- }
- frac_col_left -= frac_col_to_fill;
- frac_col_to_fill = 1.0;
- ++out_x;
- }
-
- if (frac_col_left > 0) {
- for (ch = 0; ch < channels; ++ch) {
- accum[ch] += frac_col_left * in[in_x].channel[ch];
- }
- frac_col_to_fill -= frac_col_left;
- }
- }
-
- if (out_x < out_width-1 || out_x > out_width) {
- i_fatal(3, "Internal error: out_x %d out of range (width %d)", out_x, out_width);
- }
-
- if (out_x < out_width) {
- for (ch = 0; ch < channels; ++ch) {
- accum[ch] += frac_col_to_fill * in[in_width-1].channel[ch];
- out[out_x].channel[ch] = accum[ch];
- }
- }
-}
--- /dev/null
+#include "imager.h"
+
+/*
+ * i_scale_mixing() is based on code contained in pnmscale.c, part of
+ * the netpbm distribution. No code was copied from pnmscale but
+ * the algorthm was and for this I thank the netpbm crew.
+ *
+ * Tony
+ */
+
+/* pnmscale.c - read a portable anymap and scale it
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation. This software is provided "as is" without express or
+** implied warranty.
+**
+*/
+
+
+static void
+zero_row(i_fcolor *row, int width, int channels);
+
+#code
+static void
+IM_SUFFIX(accum_output_row)(i_fcolor *accum, double fraction, IM_COLOR const *in,
+ int width, int channels);
+static void
+IM_SUFFIX(horizontal_scale)(IM_COLOR *out, int out_width,
+ i_fcolor const *in, int in_width,
+ int channels);
+#/code
+
+/*
+=item i_scale_mixing
+
+Returns a new image scaled to the given size.
+
+Unlike i_scale_axis() this does a simple coverage of pixels from
+source to target and doesn't resample.
+
+Adapted from pnmscale.
+
+=cut
+*/
+i_img *
+i_scale_mixing(i_img *src, int x_out, int y_out) {
+ i_img *result;
+ i_fcolor *accum_row = NULL;
+ int y;
+ int accum_row_bytes;
+ double rowsleft, fracrowtofill;
+ int rowsread;
+ double y_scale;
+
+ mm_log((1, "i_scale_mixing(src %p, x_out %d, y_out %d)\n",
+ src, x_out, y_out));
+
+ i_clear_error();
+
+ if (x_out <= 0) {
+ i_push_errorf(0, "output width %d invalid", x_out);
+ return NULL;
+ }
+ if (y_out <= 0) {
+ i_push_errorf(0, "output height %d invalid", y_out);
+ return NULL;
+ }
+
+ if (x_out == src->xsize && y_out == src->ysize) {
+ return i_copy(src);
+ }
+
+ y_scale = y_out / (double)src->ysize;
+
+ result = i_sametype_chans(src, x_out, y_out, src->channels);
+ if (!result)
+ return NULL;
+
+ accum_row_bytes = sizeof(i_fcolor) * src->xsize;
+ if (accum_row_bytes / sizeof(i_fcolor) != src->xsize) {
+ i_push_error(0, "integer overflow allocating accumulator row buffer");
+ return NULL;
+ }
+
+ accum_row = mymalloc(accum_row_bytes);
+
+#code src->bits <= 8
+ IM_COLOR *in_row = NULL;
+ IM_COLOR *xscale_row = NULL;
+ int in_row_bytes, out_row_bytes;
+
+ in_row_bytes = sizeof(IM_COLOR) * src->xsize;
+ if (in_row_bytes / sizeof(IM_COLOR) != src->xsize) {
+ i_push_error(0, "integer overflow allocating input row buffer");
+ return NULL;
+ }
+ out_row_bytes = sizeof(IM_COLOR) * x_out;
+ if (out_row_bytes / sizeof(IM_COLOR) != x_out) {
+ i_push_error(0, "integer overflow allocating output row buffer");
+ return NULL;
+ }
+
+ in_row = mymalloc(in_row_bytes);
+ xscale_row = mymalloc(out_row_bytes);
+
+ rowsread = 0;
+ rowsleft = 0.0;
+ for (y = 0; y < y_out; ++y) {
+ if (y_out == src->ysize) {
+ /* no vertical scaling, just load it */
+ int x, ch;
+#ifdef IM_EIGHT_BIT
+ /* load and convert to doubles */
+ IM_GLIN(src, 0, src->xsize, y, in_row);
+ for (x = 0; x < src->xsize; ++x) {
+ for (ch = 0; ch < src->channels; ++ch) {
+ accum_row[x].channel[ch] = in_row[x].channel[ch];
+ }
+ }
+#else
+ IM_GLIN(src, 0, src->xsize, y, accum_row);
+#endif
+ }
+ else {
+ fracrowtofill = 1.0;
+ zero_row(accum_row, src->xsize, src->channels);
+ while (fracrowtofill > 0) {
+ if (rowsleft <= 0) {
+ if (rowsread < src->ysize) {
+ IM_GLIN(src, 0, src->xsize, rowsread, in_row);
+ ++rowsread;
+ }
+ /* else just use the last row read */
+
+ rowsleft = y_scale;
+ }
+ if (rowsleft < fracrowtofill) {
+ IM_SUFFIX(accum_output_row)(accum_row, rowsleft, in_row,
+ src->xsize, src->channels);
+ fracrowtofill -= rowsleft;
+ rowsleft = 0;
+ }
+ else {
+ IM_SUFFIX(accum_output_row)(accum_row, fracrowtofill, in_row,
+ src->xsize, src->channels);
+ rowsleft -= fracrowtofill;
+ fracrowtofill = 0;
+ }
+ }
+ }
+ /* we've accumulated a vertically scaled row */
+ if (x_out == src->xsize) {
+ int x, ch;
+#if IM_EIGHT_BIT
+ /* no need to scale, but we need to convert it */
+ for (x = 0; x < x_out; ++x) {
+ for (ch = 0; ch < result->channels; ++ch)
+ xscale_row[x].channel[ch] = accum_row[x].channel[ch];
+ }
+ IM_PLIN(result, 0, x_out, y, xscale_row);
+#else
+ IM_PLIN(result, 0, x_out, y, accum_row);
+#endif
+ }
+ else {
+ IM_SUFFIX(horizontal_scale)(xscale_row, x_out, accum_row,
+ src->xsize, src->channels);
+ IM_PLIN(result, 0, x_out, y, xscale_row);
+ }
+ }
+ myfree(in_row);
+ myfree(xscale_row);
+#/code
+ myfree(accum_row);
+
+ return result;
+}
+
+static void
+zero_row(i_fcolor *row, int width, int channels) {
+ int x;
+ int ch;
+
+ /* with IEEE floats we could just use memset() but that's not
+ safe in general under ANSI C.
+ memset() is slightly faster.
+ */
+ for (x = 0; x < width; ++x) {
+ for (ch = 0; ch < channels; ++ch)
+ row[x].channel[ch] = 0.0;
+ }
+}
+
+#code
+
+static void
+IM_SUFFIX(accum_output_row)(i_fcolor *accum, double fraction, IM_COLOR const *in,
+ int width, int channels) {
+ int x, ch;
+
+ /* it's tempting to change this into a pointer iteration loop but
+ modern CPUs do the indexing as part of the instruction */
+ for (x = 0; x < width; ++x) {
+ for (ch = 0; ch < channels; ++ch) {
+ accum[x].channel[ch] += in[x].channel[ch] * fraction;
+ }
+ }
+}
+
+static void
+IM_SUFFIX(horizontal_scale)(IM_COLOR *out, int out_width,
+ i_fcolor const *in, int in_width,
+ int channels) {
+ double frac_col_to_fill, frac_col_left;
+ int in_x;
+ int out_x;
+ double x_scale = (double)out_width / in_width;
+ int ch;
+ double accum[MAXCHANNELS] = { 0 };
+
+ frac_col_to_fill = 1.0;
+ out_x = 0;
+ for (in_x = 0; in_x < in_width; ++in_x) {
+ frac_col_left = x_scale;
+ while (frac_col_left >= frac_col_to_fill) {
+ for (ch = 0; ch < channels; ++ch)
+ accum[ch] += frac_col_to_fill * in[in_x].channel[ch];
+
+ for (ch = 0; ch < channels; ++ch) {
+ out[out_x].channel[ch] = accum[ch];
+ accum[ch] = 0;
+ }
+ frac_col_left -= frac_col_to_fill;
+ frac_col_to_fill = 1.0;
+ ++out_x;
+ }
+
+ if (frac_col_left > 0) {
+ for (ch = 0; ch < channels; ++ch) {
+ accum[ch] += frac_col_left * in[in_x].channel[ch];
+ }
+ frac_col_to_fill -= frac_col_left;
+ }
+ }
+
+ if (out_x < out_width-1 || out_x > out_width) {
+ i_fatal(3, "Internal error: out_x %d out of range (width %d)", out_x, out_width);
+ }
+
+ if (out_x < out_width) {
+ for (ch = 0; ch < channels; ++ch) {
+ accum[ch] += frac_col_to_fill * in[in_width-1].channel[ch];
+ out[out_x].channel[ch] = accum[ch];
+ }
+ }
+}
+
+#/code
#!perl -w
use strict;
use lib 't';
-use Test::More tests => 213;
+use Test::More tests => 223;
BEGIN { use_ok(Imager=>':all') }
ok($scaleimg->write(file=>'testout/t40scale3.ppm', type=>'pnm'),
"write mixing scaled image") or print "# ", $img->errstr, "\n";
+{ # double image scaling with mixing, since it has code to handle it
+ my $dimg = Imager->new(xsize => $img->getwidth, ysize => $img->getheight,
+ channels => $img->getchannels,
+ bits => 'double');
+ ok($dimg, "create double/sample image");
+ $dimg->paste(src => $img);
+ $scaleimg = $dimg->scale(scalefactor => 0.25, qtype => 'mixing');
+ ok($scaleimg, "scale it (mixing, double)");
+ ok($scaleimg->write(file => 'testout/t40mixdbl.ppm', type => 'pnm'),
+ "write double/mixing scaled image");
+ is($scaleimg->bits, 'double', "got the right image type as output");
+
+ # hscale only, mixing
+ $scaleimg = $dimg->scale(xscalefactor => 0.33, yscalefactor => 1.0,
+ qtype => 'mixing');
+ ok($scaleimg, "scale it (hscale, mixing, double)");
+ is($scaleimg->getheight, $dimg->getheight, "same height");
+ ok($scaleimg->write(file => 'testout/t40hscdmix.ppm', type => 'pnm'),
+ "save it");
+
+ # vscale only, mixing
+ $scaleimg = $dimg->scale(xscalefactor => 1.0, yscalefactor => 0.33,
+ qtype => 'mixing');
+ ok($scaleimg, "scale it (vscale, mixing, double)");
+ is($scaleimg->getwidth, $dimg->getwidth, "same width");
+ ok($scaleimg->write(file => 'testout/t40vscdmix.ppm', type => 'pnm'),
+ "save it");
+}
+
{
# check for a warning when scale() is called in void context
my $warning;