You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegraphics/kviewshell/plugins/djvu/libdjvu/GScaler.cpp

707 lines
19 KiB

//C- -*- C++ -*-
//C- -------------------------------------------------------------------
//C- DjVuLibre-3.5
//C- Copyright (c) 2002 Leon Bottou and Yann Le Cun.
//C- Copyright (c) 2001 AT&T
//C-
//C- This software is subject to, and may be distributed under, the
//C- GNU General Public License, Version 2. The license should have
//C- accompanied the software or you may obtain a copy of the license
//C- from the Free Software Foundation at http://www.fsf.org .
//C-
//C- This program is distributed in the hope that it will be useful,
//C- but WITHOUT ANY WARRANTY; without even the implied warranty of
//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//C- GNU General Public License for more details.
//C-
//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library
//C- distributed by Lizardtech Software. On July 19th 2002, Lizardtech
//C- Software authorized us to replace the original DjVu(r) Reference
//C- Library notice by the following text (see doc/lizard2002.djvu):
//C-
//C- ------------------------------------------------------------------
//C- | DjVu (r) Reference Library (v. 3.5)
//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
//C- | The DjVu Reference Library is protected by U.S. Pat. No.
//C- | 6,058,214 and patents pending.
//C- |
//C- | This software is subject to, and may be distributed under, the
//C- | GNU General Public License, Version 2. The license should have
//C- | accompanied the software or you may obtain a copy of the license
//C- | from the Free Software Foundation at http://www.fsf.org .
//C- |
//C- | The computer code originally released by LizardTech under this
//C- | license and unmodified by other parties is deemed "the LIZARDTECH
//C- | ORIGINAL CODE." Subject to any third party intellectual property
//C- | claims, LizardTech grants recipient a worldwide, royalty-free,
//C- | non-exclusive license to make, use, sell, or otherwise dispose of
//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the
//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU
//C- | General Public License. This grant only confers the right to
//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to
//C- | the extent such infringement is reasonably necessary to enable
//C- | recipient to make, have made, practice, sell, or otherwise dispose
//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to
//C- | any greater extent that may be necessary to utilize further
//C- | modifications or combinations.
//C- |
//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//C- +------------------------------------------------------------------
//
// $Id: GScaler.cpp,v 1.11 2004/06/03 14:15:18 leonb Exp $
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#if NEED_GNUG_PRAGMAS
# pragma implementation
#endif
// Rescale images with fast bilinear interpolation
// From: Leon Bottou, 1/31/2002
// Almost equal to my initial code.
#include "GScaler.h"
#ifdef HAVE_NAMESPACES
namespace DJVU {
# ifdef NOT_DEFINED // Just to fool emacs c++ mode
}
#endif
#endif
////////////////////////////////////////
// CONSTANTS
#define FRACBITS 4
#define FRACSIZE (1<<FRACBITS)
#define FRACSIZE2 (FRACSIZE>>1)
#define FRACMASK (FRACSIZE-1)
////////////////////////////////////////
// UTILITIES
static int interp_ok = 0;
static short interp[FRACSIZE][512];
static void
prepare_interp()
{
if (! interp_ok)
{
interp_ok = 1;
for (int i=0; i<FRACSIZE; i++)
{
short *deltas = & interp[i][256];
for (int j = -255; j <= 255; j++)
deltas[j] = ( j*i + FRACSIZE2 ) >> FRACBITS;
}
}
}
static inline int
mini(int x, int y)
{
return (x < y ? x : y);
}
static inline int
maxi(int x, int y)
{
return (x > y ? x : y);
}
////////////////////////////////////////
// GSCALER
GScaler::GScaler()
: inw(0), inh(0),
xshift(0), yshift(0), redw(0), redh(0),
outw(0), outh(0),
gvcoord(vcoord,0), ghcoord(hcoord,0)
{
}
GScaler::~GScaler()
{
}
void
GScaler::set_input_size(int w, int h)
{
inw = w;
inh = h;
if (vcoord)
{
gvcoord.resize(0);
}
if (hcoord)
{
ghcoord.resize(0);
}
}
void
GScaler::set_output_size(int w, int h)
{
outw = w;
outh = h;
if (vcoord)
{
gvcoord.resize(0);
}
if (hcoord)
{
ghcoord.resize(0);
}
}
static void
prepare_coord(int *coord, int inmax, int outmax, int in, int out)
{
int len = (in*FRACSIZE);
int beg = (len+out)/(2*out) - FRACSIZE2;
// Bresenham algorithm
int y = beg;
int z = out/2;
int inmaxlim = (inmax-1)*FRACSIZE;
for (int x=0; x<outmax; x++)
{
coord[x] = mini(y,inmaxlim);
z = z + len;
y = y + z / out;
z = z % out;
}
// Result must fit exactly
if (out==outmax && y!=beg+len)
G_THROW( ERR_MSG("GScaler.assertion") );
}
void
GScaler::set_horz_ratio(int numer, int denom)
{
if (! (inw>0 && inh>0 && outw>0 && outh>0))
G_THROW( ERR_MSG("GScaler.undef_size") );
// Implicit ratio (determined by the input/output sizes)
if (numer==0 && denom==0) {
numer = outw;
denom = inw;
} else if (numer<=0 || denom<=0)
G_THROW( ERR_MSG("GScaler.ratios") );
// Compute horz reduction
xshift = 0;
redw = inw;
while (numer+numer < denom) {
xshift += 1;
redw = (redw + 1) >> 1;
numer = numer << 1;
}
// Compute coordinate table
if (! hcoord)
ghcoord.resize(outw);
prepare_coord(hcoord, redw, outw, denom, numer);
}
void
GScaler::set_vert_ratio(int numer, int denom)
{
if (! (inw>0 && inh>0 && outw>0 && outh>0))
G_THROW( ERR_MSG("GScaler.undef_size") );
// Implicit ratio (determined by the input/output sizes)
if (numer==0 && denom==0) {
numer = outh;
denom = inh;
} else if (numer<=0 || denom<=0)
G_THROW( ERR_MSG("GScaler.ratios") );
// Compute horz reduction
yshift = 0;
redh = inh;
while (numer+numer < denom) {
yshift += 1;
redh = (redh + 1) >> 1;
numer = numer << 1;
}
// Compute coordinate table
if (! vcoord)
{
gvcoord.resize(outh);
}
prepare_coord(vcoord, redh, outh, denom, numer);
}
void
GScaler::make_rectangles(const GRect &desired, GRect &red, GRect &inp)
{
// Parameter validation
if (desired.xmin<0 || desired.ymin<0 ||
desired.xmax>outw || desired.ymax>outh )
G_THROW( ERR_MSG("GScaler.too_big") );
// Compute ratio (if not done yet)
if (!vcoord)
set_vert_ratio(0,0);
if (!hcoord)
set_horz_ratio(0,0);
// Compute reduced bounds
red.xmin = (hcoord[desired.xmin]) >> FRACBITS;
red.ymin = (vcoord[desired.ymin]) >> FRACBITS;
red.xmax = (hcoord[desired.xmax-1]+FRACSIZE-1) >> FRACBITS;
red.ymax = (vcoord[desired.ymax-1]+FRACSIZE-1) >> FRACBITS;
// Borders
red.xmin = maxi(red.xmin, 0);
red.xmax = mini(red.xmax+1, redw);
red.ymin = maxi(red.ymin, 0);
red.ymax = mini(red.ymax+1, redh);
// Input
inp.xmin = maxi(red.xmin<<xshift, 0);
inp.xmax = mini(red.xmax<<xshift, inw);
inp.ymin = maxi(red.ymin<<yshift, 0);
inp.ymax = mini(red.ymax<<yshift, inh);
}
void
GScaler::get_input_rect( const GRect &desired_output, GRect &required_input )
{
GRect red;
make_rectangles(desired_output, red, required_input);
}
////////////////////////////////////////
// GBITMAPSCALER
GBitmapScaler::GBitmapScaler()
: glbuffer(lbuffer,0), gconv(conv,0), gp1(p1,0), gp2(p2,0)
{
}
GBitmapScaler::GBitmapScaler(int inw, int inh, int outw, int outh)
: glbuffer(lbuffer,0), gconv(conv,0), gp1(p1,0), gp2(p2,0)
{
set_input_size(inw, inh);
set_output_size(outw, outh);
}
GBitmapScaler::~GBitmapScaler()
{
}
unsigned char *
GBitmapScaler::get_line(int fy,
const GRect &required_red,
const GRect &provided_input,
const GBitmap &input )
{
if (fy < required_red.ymin)
fy = required_red.ymin;
else if (fy >= required_red.ymax)
fy = required_red.ymax - 1;
// Cached line
if (fy == l2)
return p2;
if (fy == l1)
return p1;
// Shift
unsigned char *p = p1;
p1 = p2;
l1 = l2;
p2 = p;
l2 = fy;
if (xshift==0 && yshift==0)
{
// Fast mode
int dx = required_red.xmin-provided_input.xmin;
int dx1 = required_red.xmax-provided_input.xmin;
const unsigned char *inp1 = input[fy-provided_input.ymin] + dx;
while (dx++ < dx1)
*p++ = conv[*inp1++];
return p2;
}
else
{
// Compute location of line
GRect line;
line.xmin = required_red.xmin << xshift;
line.xmax = required_red.xmax << xshift;
line.ymin = fy << yshift;
line.ymax = (fy+1) << yshift;
line.intersect(line, provided_input);
line.translate(-provided_input.xmin, -provided_input.ymin);
// Prepare variables
const unsigned char *botline = input[line.ymin];
int rowsize = input.rowsize();
int sw = 1<<xshift;
int div = xshift+yshift;
int rnd = 1<<(div-1);
// Compute averages
for (int x=line.xmin; x<line.xmax; x+=sw,p++)
{
int g=0, s=0;
const unsigned char *inp0 = botline + x;
int sy1 = mini(line.height(), (1<<yshift));
for (int sy=0; sy<sy1; sy++,inp0+=rowsize)
{
const unsigned char *inp1;
const unsigned char *inp2 = inp0 + mini(x+sw, line.xmax) - x;
for (inp1=inp0; inp1<inp2; inp1++)
{
g += conv[*inp1];
s += 1;
}
}
if (s == rnd+rnd)
*p = (g+rnd)>>div;
else
*p = (g+s/2)/s;
}
// Return
return p2;
}
}
void
GBitmapScaler::scale( const GRect &provided_input, const GBitmap &input,
const GRect &desired_output, GBitmap &output )
{
// Compute rectangles
GRect required_input;
GRect required_red;
make_rectangles(desired_output, required_red, required_input);
// Parameter validation
if (provided_input.width() != (int)input.columns() ||
provided_input.height() != (int)input.rows() )
G_THROW( ERR_MSG("GScaler.no_match") );
if (provided_input.xmin > required_input.xmin ||
provided_input.ymin > required_input.ymin ||
provided_input.xmax < required_input.xmax ||
provided_input.ymax < required_input.ymax )
G_THROW( ERR_MSG("GScaler.too_small") );
// Adjust output pixmap
if (desired_output.width() != (int)output.columns() ||
desired_output.height() != (int)output.rows() )
output.init(desired_output.height(), desired_output.width());
output.set_grays(256);
// Prepare temp stuff
gp1.resize(0);
gp2.resize(0);
glbuffer.resize(0);
prepare_interp();
const int bufw = required_red.width();
glbuffer.resize(bufw+2);
gp1.resize(bufw);
gp2.resize(bufw);
l1 = l2 = -1;
// Prepare gray conversion array (conv)
gconv.resize(0);
gconv.resize(256);
int maxgray = input.get_grays()-1;
for (int i=0; i<256; i++)
{
conv[i]=(i<= maxgray)
?(((i*255) + (maxgray>>1)) / maxgray)
:255;
}
// Loop on output lines
for (int y=desired_output.ymin; y<desired_output.ymax; y++)
{
// Perform vertical interpolation
{
int fy = vcoord[y];
int fy1 = fy>>FRACBITS;
int fy2 = fy1+1;
const unsigned char *lower, *upper;
// Obtain upper and lower line in reduced image
lower = get_line(fy1, required_red, provided_input, input);
upper = get_line(fy2, required_red, provided_input, input);
// Compute line
unsigned char *dest = lbuffer+1;
const short *deltas = & interp[fy&FRACMASK][256];
for(unsigned char const * const edest=(unsigned char const *)dest+bufw;
dest<edest;upper++,lower++,dest++)
{
const int l = *lower;
const int u = *upper;
*dest = l + deltas[u-l];
}
}
// Perform horizontal interpolation
{
// Prepare for side effects
lbuffer[0] = lbuffer[1];
lbuffer[bufw] = lbuffer[bufw];
unsigned char *line = lbuffer+1-required_red.xmin;
unsigned char *dest = output[y-desired_output.ymin];
// Loop horizontally
for (int x=desired_output.xmin; x<desired_output.xmax; x++)
{
int n = hcoord[x];
const unsigned char *lower = line + (n>>FRACBITS);
const short *deltas = &interp[n&FRACMASK][256];
int l = lower[0];
int u = lower[1];
*dest = l + deltas[u-l];
dest++;
}
}
}
// Free temporaries
gp1.resize(0);
gp2.resize(0);
glbuffer.resize(0);
gconv.resize(0);
}
////////////////////////////////////////
// GPIXMAPSCALER
GPixmapScaler::GPixmapScaler()
: glbuffer((void *&)lbuffer,0,sizeof(GPixel)),
gp1((void *&)p1,0,sizeof(GPixel)),
gp2((void *&)p2,0,sizeof(GPixel))
{
}
GPixmapScaler::GPixmapScaler(int inw, int inh, int outw, int outh)
: glbuffer((void *&)lbuffer,0,sizeof(GPixel)),
gp1((void *&)p1,0,sizeof(GPixel)),
gp2((void *&)p2,0,sizeof(GPixel))
{
set_input_size(inw, inh);
set_output_size(outw, outh);
}
GPixmapScaler::~GPixmapScaler()
{
}
GPixel *
GPixmapScaler::get_line(int fy,
const GRect &required_red,
const GRect &provided_input,
const GPixmap &input )
{
if (fy < required_red.ymin)
fy = required_red.ymin;
else if (fy >= required_red.ymax)
fy = required_red.ymax - 1;
// Cached line
if (fy == l2)
return p2;
if (fy == l1)
return p1;
// Shift
GPixel *p=p1;
p1 = p2;
l1 = l2;
p2 = p;
l2 = fy;
// Compute location of line
GRect line;
line.xmin = required_red.xmin << xshift;
line.xmax = required_red.xmax << xshift;
line.ymin = fy << yshift;
line.ymax = (fy+1) << yshift;
line.intersect(line, provided_input);
line.translate(-provided_input.xmin, -provided_input.ymin);
// Prepare variables
const GPixel *botline = input[line.ymin];
int rowsize = input.rowsize();
int sw = 1<<xshift;
int div = xshift+yshift;
int rnd = 1<<(div-1);
// Compute averages
for (int x=line.xmin; x<line.xmax; x+=sw,p++)
{
int r=0, g=0, b=0, s=0;
const GPixel *inp0 = botline + x;
int sy1 = mini(line.height(), (1<<yshift));
for (int sy=0; sy<sy1; sy++,inp0+=rowsize)
{
const GPixel *inp1;
const GPixel *inp2 = inp0 + mini(x+sw, line.xmax) - x;
for (inp1 = inp0; inp1<inp2; inp1++)
{
r += inp1->r;
g += inp1->g;
b += inp1->b;
s += 1;
}
}
if (s == rnd+rnd)
{
p->r = (r+rnd) >> div;
p->g = (g+rnd) >> div;
p->b = (b+rnd) >> div;
}
else
{
p->r = (r+s/2)/s;
p->g = (g+s/2)/s;
p->b = (b+s/2)/s;
}
}
// Return
return (GPixel *)p2;
}
void
GPixmapScaler::scale( const GRect &provided_input, const GPixmap &input,
const GRect &desired_output, GPixmap &output )
{
// Compute rectangles
GRect required_input;
GRect required_red;
make_rectangles(desired_output, required_red, required_input);
// Parameter validation
if (provided_input.width() != (int)input.columns() ||
provided_input.height() != (int)input.rows() )
G_THROW( ERR_MSG("GScaler.no_match") );
if (provided_input.xmin > required_input.xmin ||
provided_input.ymin > required_input.ymin ||
provided_input.xmax < required_input.xmax ||
provided_input.ymax < required_input.ymax )
G_THROW( ERR_MSG("GScaler.too_small") );
// Adjust output pixmap
if (desired_output.width() != (int)output.columns() ||
desired_output.height() != (int)output.rows() )
output.init(desired_output.height(), desired_output.width());
// Prepare temp stuff
gp1.resize(0,sizeof(GPixel));
gp2.resize(0,sizeof(GPixel));
glbuffer.resize(0,sizeof(GPixel));
prepare_interp();
const int bufw = required_red.width();
glbuffer.resize(bufw+2,sizeof(GPixel));
if (xshift>0 || yshift>0)
{
gp1.resize(bufw,sizeof(GPixel));
gp2.resize(bufw,sizeof(GPixel));
l1 = l2 = -1;
}
// Loop on output lines
for (int y=desired_output.ymin; y<desired_output.ymax; y++)
{
// Perform vertical interpolation
{
int fy = vcoord[y];
int fy1 = fy>>FRACBITS;
int fy2 = fy1+1;
const GPixel *lower, *upper;
// Obtain upper and lower line in reduced image
if (xshift>0 || yshift>0)
{
lower = get_line(fy1, required_red, provided_input, input);
upper = get_line(fy2, required_red, provided_input, input);
}
else
{
int dx = required_red.xmin-provided_input.xmin;
fy1 = maxi(fy1, required_red.ymin);
fy2 = mini(fy2, required_red.ymax-1);
lower = input[fy1-provided_input.ymin] + dx;
upper = input[fy2-provided_input.ymin] + dx;
}
// Compute line
GPixel *dest = lbuffer+1;
const short *deltas = & interp[fy&FRACMASK][256];
for(GPixel const * const edest = (GPixel const *)dest+bufw;
dest<edest;upper++,lower++,dest++)
{
const int lower_r = lower->r;
const int delta_r = deltas[(int)upper->r - lower_r];
dest->r = lower_r + delta_r;
const int lower_g = lower->g;
const int delta_g = deltas[(int)upper->g - lower_g];
dest->g = lower_g + delta_g;
const int lower_b = lower->b;
const int delta_b = deltas[(int)upper->b - lower_b];
dest->b = lower_b + delta_b;
}
}
// Perform horizontal interpolation
{
// Prepare for side effects
lbuffer[0] = lbuffer[1];
lbuffer[bufw] = lbuffer[bufw];
GPixel *line = lbuffer+1-required_red.xmin;
GPixel *dest = output[y-desired_output.ymin];
// Loop horizontally
for (int x=desired_output.xmin; x<desired_output.xmax; x++,dest++)
{
const int n = hcoord[x];
const GPixel *lower = line + (n>>FRACBITS);
const short *deltas = &interp[n&FRACMASK][256];
const int lower_r = lower[0].r;
const int delta_r = deltas[(int)lower[1].r - lower_r];
dest->r = lower_r + delta_r;
const int lower_g = lower[0].g;
const int delta_g = deltas[(int)lower[1].g - lower_g];
dest->g = lower_g + delta_g;
const int lower_b = lower[0].b;
const int delta_b = deltas[(int)lower[1].b - lower_b];
dest->b = lower_b + delta_b;
}
}
}
// Free temporaries
gp1.resize(0,sizeof(GPixel));
gp2.resize(0,sizeof(GPixel));
glbuffer.resize(0,sizeof(GPixel));
}
#ifdef HAVE_NAMESPACES
}
# ifndef NOT_USING_DJVU_NAMESPACE
using namespace DJVU;
# endif
#endif