/**
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info: http://www.jpedal.org
* (C) Copyright 1997-2008, IDRsolutions and Contributors.
*
* This file is part of JPedal
*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ---------------
* ImageTransformerDouble.java
* ---------------
*/
package org.jpedal.images;
import org.jpedal.PdfDecoder;
import org.jpedal.color.ColorSpaces;
import org.jpedal.io.ColorSpaceConvertor;
import org.jpedal.objects.GraphicsState;
import org.jpedal.utils.LogWriter;
import org.jpedal.utils.Matrix;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
/**
* class to shrink and clip an extracted image
* On reparse just calculates co-ords
*/
public class ImageTransformerDouble {
double ny=0,nx=0;
/**the clip*/
private Area clip=null;
/**holds the actual image*/
private BufferedImage current_image;
/**matrices used in transformation*/
private float[][] Trm,Trm1, Trm2,CTM;
/**image co-ords*/
private int i_x = 0, i_y = 0, i_w = 0, i_h = 0;
/**dpi scale factor*/
private float scale=1;
private boolean scaleImage;
/**flag to show image clipped*/
private boolean hasClip=false;
/**tell software to remove unneeded border on hires clip*/
private boolean removeClippedBorder=true;
/**
* pass in image information and apply transformation matrix
* to image
*/
public ImageTransformerDouble(
int dpi,
GraphicsState currentGS,
BufferedImage new_image,
boolean scaleImage,boolean removeClippedBorder) {
this.removeClippedBorder=removeClippedBorder;
/**
* set values
*/
//set scaling for final image
scale=dpi/72;
//save global values
this.current_image = new_image;
this.scaleImage=scaleImage;
CTM = currentGS.CTM; //local copy of CTM
createMatrices();
// get clipped image and co-ords
if(currentGS.getClippingShape()!=null)
clip = (Area) currentGS.getClippingShape().clone();
calcCoordinates();
}
/**
* applies the shear/rotate of a double transformation to the clipped image
*/
final public void doubleScaleTransformShear(boolean alreadyTurned){
float[][] Trm1=this.Trm1;
boolean isSwapped=false;
scale(Trm1);
current_image = ColorSpaceConvertor.convertToARGB(current_image);
//create a copy of clip (so we don't alter clip)
if(!isSwapped && clip!=null){
Area final_clip = (Area) clip.clone();
clipImage(true,final_clip,alreadyTurned);
i_x=(int)clip.getBounds2D().getMinX();
i_y=(int) clip.getBounds2D().getMinY();
i_w=(int)((clip.getBounds2D().getMaxX())-i_x);
i_h=(int)((clip.getBounds2D().getMaxY())-i_y);
}
}
/**
* applies the scale of a double transformation to the clipped image
*/
final public void doubleScaleTransformScale(){
if((CTM[0][0]!=0.0)&(CTM[1][1]!=0.0))
scale(Trm2);
//clipImage(true);
}
/**complete image and workout co-ordinates*/
final public void completeImage(){
//Matrix.show(CTM);
/*if((CTM[0][1]>0 )&(CTM[1][0]>0 )){
//ShowGUIMessage.showGUIMessage("",current_image,"a ");
AffineTransform image_at =new AffineTransform();
image_at.scale(-1,-1);
image_at.translate(-current_image.getWidth(),-current_image.getHeight());
AffineTransformOp invert= new AffineTransformOp(image_at, ColorSpaces.hints);
current_image = invert.filter(current_image,null);
}*/
//ShowGUIMessage.showGUIMessage("",current_image,"a ");
/**/
if(hasClip){
i_x=(int)clip.getBounds2D().getMinX();
i_y=(int) clip.getBounds2D().getMinY();
i_w=(current_image.getWidth());
i_h=(current_image.getHeight());
//System.out.println(current_image.getWidth()+" "+current_image.getHeight());
//System.out.println(i_x+" "+i_y+" "+i_w+" "+i_h+" "+clip.getBounds2D());
}/***/
}
/**scale image to size*/
private void scale(float[][] Trm){
/**
* transform the image only if needed
*/
if ((Trm[0][0] != 1.0)|| (Trm[1][1] != 1.0)|| (Trm[0][1] != 0.0)
|| (Trm[1][0] != 0.0)||(scale!=1)) {
int w = current_image.getWidth(); //raw width
int h = current_image.getHeight(); //raw height
//workout transformation for the image
AffineTransform image_at =new AffineTransform(Trm[0][0]*scale,-Trm[0][1]*scale,-Trm[1][0]*scale,Trm[1][1]*scale,0,0);
//apply it to the shape first so we can align
Area r =new Area(new Rectangle(0,0,w,h));
r.transform(image_at);
//make sure it fits onto image (must start at 0,0)
ny = r.getBounds2D().getY();
nx = r.getBounds2D().getX();
image_at =new AffineTransform(Trm[0][0]*scale,-Trm[0][1]*scale,-Trm[1][0]*scale,Trm[1][1]*scale,-nx,-ny);
//Create the affine operation.
//ColorSpaces.hints causes single lines to vanish);
AffineTransformOp invert;
if((w>10)&(h>10))
invert = new AffineTransformOp(image_at, ColorSpaces.hints);
else
invert = new AffineTransformOp(image_at, null);
//scale image to produce final version
if(scaleImage)
current_image = invert.filter(current_image,null);
}
}
/**workout the transformation as 1 or 2 transformations*/
private void createMatrices(){
int w = current_image.getWidth(); //raw width
int h = current_image.getHeight(); //raw height
//build transformation matrix by hand to avoid errors in rounding
Trm = new float[3][3];
Trm[0][0] = (CTM[0][0] / w);
Trm[0][1] = (CTM[0][1] / w);
Trm[0][2] = 0;
Trm[1][0] = (CTM[1][0] / h);
Trm[1][1] = (CTM[1][1] / h);
Trm[1][2] = 0;
Trm[2][0] = CTM[2][0];
Trm[2][1] = CTM[2][1];
Trm[2][2] = 1;
//round numbers if close to 1
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 3; x++) {
if ((Trm[x][y] > .99) & (Trm[x][y] < 1))
Trm[x][y] = 1;
}
}
/**now work out as 2 matrices*/
Trm1=new float[3][3];
Trm2=new float[3][3];
//used to handle sheer
float x1=1,x2=0,y1=1,y2=0;
x1=CTM[0][0];
if(x1<0)
x1=-x1;
x2=CTM[0][1];
if(x2<0)
x2=-x2;
y1=CTM[1][1];
if(y1<0)
y1=-y1;
y2=CTM[1][0];
if(y2<0)
y2=-y2;
//factor out scaling to produce just the sheer/rotation
if((CTM[0][0]==0.0)|(CTM[1][1]==0.0)){
Trm1=Trm;
}else if((CTM[0][1]==0.0)&&(CTM[1][0]==0.0)){
Trm1[0][0] = w/(CTM[0][0]);
Trm1[0][1] = 0;
Trm1[0][2] = 0;
Trm1[1][0] =0;
Trm1[1][1] = h/(CTM[1][1]);
Trm1[1][2] = 0;
Trm1[2][0] = 0;
Trm1[2][1] = 0;
Trm1[2][2] = 1;
Trm1=Matrix.multiply(Trm,Trm1);
//round numbers if close to 1
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 3; x++) {
if ((Trm1[x][y] > .99) & (Trm1[x][y] < 1))
Trm1[x][y] = 1;
}
}
}else{ //its got sheer/rotation
if(x1>x2)
Trm1[0][0] = w/(CTM[0][0]);
else
Trm1[0][0] = w/(CTM[0][1]);
if (Trm1[0][0]<0)
Trm1[0][0]=-Trm1[0][0];
Trm1[0][1] = 0;
Trm1[0][2] = 0;
Trm1[1][0] =0;
if(y1>y2)
Trm1[1][1] = h/(CTM[1][1]);
else
Trm1[1][1] = h/(CTM[1][0]);
if (Trm1[1][1]<0)
Trm1[1][1]=-Trm1[1][1];
Trm1[1][2] = 0;
Trm1[2][0] = 0;
Trm1[2][1] = 0;
Trm1[2][2] = 1;
Trm1=Matrix.multiply(Trm,Trm1);
//round numbers if close to 1
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 3; x++) {
if ((Trm1[x][y] > .99) & (Trm1[x][y] < 1))
Trm1[x][y] = 1;
}
}
}
//create a transformation with just the scaling
if(x1>x2)
Trm2[0][0] = (CTM[0][0] / w);
else
Trm2[0][0] = (CTM[0][1] / w);
if(Trm2[0][0] <0)
Trm2[0][0] =-Trm2[0][0] ;
Trm2[0][1] = 0;
Trm2[0][2] = 0;
Trm2[1][0] = 0;
if(y1>y2)
Trm2[1][1] = (CTM[1][1] / h);
else
Trm2[1][1] = (CTM[1][0] / h);
if(Trm2[1][1] <0)
Trm2[1][1] =-Trm2[1][1] ;
Trm2[1][2] = 0;
Trm2[2][0] = 0;
Trm2[2][1] = 0;
Trm2[2][2] = 1;
//round numbers if close to 1
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 3; x++) {
if ((Trm2[x][y] > .99) & (Trm2[x][y] < 1))
Trm2[x][y] = 1;
}
}
}
/**
* workout correct screen co-ords allow for rotation
*/
private final void calcCoordinates(){
if ((CTM[1][0] == 0) &( (CTM[0][1] == 0))){
i_x = (int) CTM[2][0];
i_y = (int) CTM[2][1];
i_w =(int) CTM[0][0];
i_h =(int) CTM[1][1];
if(i_w<0)
i_w=-i_w;
if(i_h<0)
i_h=-i_h;
}else{ //some rotation/skew
i_w=(int) (Math.sqrt((CTM[0][0] * CTM[0][0]) + (CTM[0][1] * CTM[0][1])));
i_h =(int) (Math.sqrt((CTM[1][1] * CTM[1][1]) + (CTM[1][0] * CTM[1][0])));
if((CTM[1][0]>0)&(CTM[0][1]<0)){
i_x = (int) (CTM[2][0]);
i_y = (int) (CTM[2][1]+CTM[0][1]);
//System.err.println("AA "+i_w+" "+i_h);
}else if((CTM[1][0]<0)&(CTM[0][1]>0)){
i_x = (int) (CTM[2][0]+CTM[1][0]);
i_y = (int) (CTM[2][1]);
//System.err.println("BB "+i_w+" "+i_h);
}else if((CTM[1][0]>0)&(CTM[0][1]>0)){
i_x = (int) (CTM[2][0]);
i_y = (int) (CTM[2][1]);
//System.err.println("CC "+i_w+" "+i_h);
}else{
//System.err.println("DD "+i_w+" "+i_h);
i_x = (int) (CTM[2][0]);
i_y =(int) (CTM[2][1]);
}
}
//System.err.println(i_x+" "+i_y+" "+i_w+" "+i_h);
//Matrix.show(CTM);
//alter to allow for back to front or reversed
if ( CTM[1][1]< 0)
i_y = i_y - i_h;
if ( CTM[0][0]< 0)
i_x = i_x - i_w;
//ShowGUIMessage.showGUIMessage("",current_image,"xx="+i_x+" "+i_y+" "+i_w+" "+i_h+" h="+current_image.getHeight());
}
//////////////////////////////////////////////////////////////////////////
/**
* get y of image (x1,y1 is top left)
*/
final public int getImageY() {
return i_y;
}
//////////////////////////////////////////////////////////////////////////
/**
* get image
*/
final public BufferedImage getImage() {
return current_image;
}
//////////////////////////////////////////////////////////////////////////
/**
* get width of image
*/
final public int getImageW() {
return i_w;
}
//////////////////////////////////////////////////////////////////////////
/**
* get height of image
*/
final public int getImageH() {
return i_h;
}
//////////////////////////////////////////////////////////////////////////
/**
* get X of image (x,y is top left)
*/
final public int getImageX() {
return i_x;
}
/**
* clip the image
*/
final private void clipImage(boolean singleScale, Area final_clip, boolean alreadyTurned) {
//if not working at 72 dpi, alter clip to fit
if(scale!=1){
AffineTransform af=AffineTransform.getScaleInstance(scale,scale);
final_clip.transform(af);
}
/**
* invert the clip
*/
double shape_x = final_clip.getBounds2D().getX();
double shape_y = final_clip.getBounds2D().getY();
//double shape_h = final_clip.getBounds2D().getHeight();
int image_w = current_image.getWidth();
int image_h = current_image.getHeight();
AffineTransform image_at =new AffineTransform();
image_at.scale(1,-1);
image_at.translate(0,-current_image.getHeight());
AffineTransformOp invert= new AffineTransformOp(image_at, ColorSpaces.hints);
current_image = invert.filter(current_image,null);
AffineTransform align_clip = new AffineTransform();
double dx=-(CTM[2][0])*scale,dy= -((CTM[2][1])*scale);
if(CTM[1][0]<0)
dx=dx-CTM[1][0] ;
if((CTM[0][0]<0)&&(CTM[1][0]>=0))
dx=dx-CTM[1][0] ;
if(CTM[0][1]<0)
dy=dy-CTM[0][1];
if(CTM[1][1]<0){
if(CTM[0][1]>0)
dy=dy-CTM[0][1];
else if(CTM[1][1]<0)
dy=dy-CTM[1][1];
}
align_clip.translate(dx,dy );
final_clip.transform(align_clip);
AffineTransform invert2=new AffineTransform(1/Trm2[0][0],0,0,1/Trm2[1][1],0,0);
//System.out.println("invert2="+invert2);
if((!alreadyTurned)||((Trm[1][0]==0)&&(Trm[0][1]==0)))
final_clip.transform(invert2);
//co-ords of transformed shape
//reset sizes to remove area clipped
int x = (int) final_clip.getBounds().getX();
int y = (int) final_clip.getBounds().getY();
int w = (int) final_clip.getBounds().getWidth();
int h = (int) final_clip.getBounds().getHeight();
//fix for 'mirror' image on Mac
if(x<0)
final_clip.transform(AffineTransform.getTranslateInstance(-x,0));
/**
* create inverse of clip and paint on to add transparency
*/
Area inverseClip =new Area(new Rectangle(0,0,image_w,image_h));
inverseClip.exclusiveOr(final_clip);
Graphics2D image_g2 = current_image.createGraphics(); //g2 of canvas
image_g2.setComposite(AlphaComposite.Clear);
image_g2.fill(inverseClip);
AffineTransform image_at2 =new AffineTransform();
image_at2.scale(1,-1);
image_at2.translate(0,-current_image.getHeight());
AffineTransformOp invert3= new AffineTransformOp(image_at2, ColorSpaces.hints);
//mac os needs this stage!
current_image=current_image.getSubimage(0,0,current_image.getWidth(),current_image.getHeight());
current_image = invert3.filter(current_image,null);
//Matrix.show(CTM);
//get image (now clipped )
//if(count>21)
//ShowGUIMessage.showGUIMessage("2count="+count,current_image,"2count="+count);
//count++;
//check for rounding errors
if (y < 0) {
h = h - y;
y = 0;
// System.err.println("Negative");
}else{
y=image_h-h-y;
//allow for fp error
if(y<0)
y=0;
}
if (x < 0) {
w = w - x;
x = 0;
}
if (w > image_w)
w = image_w;
if (h > image_h)
h = image_h;
if (y + h > image_h)
h = image_h - y;
if (x + w > image_w)
w = image_w - x;
//extract if needed
if(removeClippedBorder){
if((x==0)&&(y==0)&&(w==current_image.getWidth())&&(h==current_image.getHeight())){
//dont bother if no change
}else{
try {
current_image = current_image.getSubimage(x, y, w, h);
} catch (Exception e) {
LogWriter.writeLog("Exception " + e + " extracting clipped image with values x="+x+" y="+y+" w="+w+" h="+h+" from image "+current_image);
}catch(Error err){
}
}
}
/***/
//work out new co-ords from shape and current
double x1, y1;
if (i_x > shape_x)
x1 = i_x;
else
x1 = shape_x;
if (i_y > shape_y)
y1 = i_y;
else
y1 = shape_y;
i_x = (int) x1;
i_y = (int) y1;
i_w = w;
i_h = h;
}
}