org.jpedal.images.ImageTransformerDouble Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of OpenViewerFX Show documentation
Show all versions of OpenViewerFX Show documentation
An Open Source JavaFX PDF Viewer
/*
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info: http://www.idrsolutions.com
* Help section for developers at http://www.idrsolutions.com/support/
*
* (C) Copyright 1997-2016 IDRsolutions and Contributors.
*
* This file is part of JPedal/JPDF2HTML5
*
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 java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
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;
/**
* class to shrink and clip an extracted image
* On reparse just calculates co-ords
*/
public class ImageTransformerDouble {
private static final boolean debug=false;
double ny,nx;
/**the clip*/
private Area clip;
/**holds the actual image*/
private BufferedImage current_image;
/**matrices used in transformation*/
private final float[][] CTM;
private float[][] Trm,Trm1, Trm2;
/**image co-ords*/
private int i_x, i_y, i_w, i_h;
private final boolean scaleImage;
/**flag to show image clipped*/
private boolean hasClip;
float scaling=1;
final int pageRotation;
/**
* pass in image information and apply transformation matrix
* to image
*/
public ImageTransformerDouble(final GraphicsState currentGS, final BufferedImage new_image, final boolean scaleImage, final float scaling, final int pageRotation) {
//save global values
this.current_image = new_image;
this.scaleImage=scaleImage;
this.scaling=scaling;
this.pageRotation=pageRotation;
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
*/
public final void doubleScaleTransformShear(){
scale(this.Trm1);
//create a copy of clip (so we don't alter clip)
if(clip!=null){
final Area final_clip = (Area) clip.clone();
final Area unscaled_clip=getUnscaledClip((Area) clip.clone());
final int segCount=isRectangle(final_clip);
clipImage(unscaled_clip,final_clip,segCount);
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);
}else if(current_image.getType()==10){ //do not need to be argb
}else{
current_image = ColorSpaceConvertor.convertToARGB(current_image);
}
}
static int isRectangle(final Shape bounds) {
int count = 0;
final PathIterator i = bounds.getPathIterator(null);
while (!i.isDone() && count < 8) { //see if rectangle or complex clip
i.next();
count++;
}
return count;
}
/**
* applies the scale of a double transformation to the clipped image
*/
public final void doubleScaleTransformScale(){
if((CTM[0][0]!=0.0)&(CTM[1][1]!=0.0)) {
scale(Trm2);
}
}
/**complete image and workout co-ordinates*/
public final void completeImage(){
if(hasClip){
i_x=(int)clip.getBounds2D().getMinX();
i_y=(int) clip.getBounds2D().getMinY();
i_w=(current_image.getWidth());
i_h=(current_image.getHeight());
}
}
/**scale image to size*/
private void scale(final 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) {
final int w = current_image.getWidth(); //raw width
final int h = current_image.getHeight(); //raw height
//workout transformation for the image
AffineTransform image_at =new AffineTransform(Trm[0][0],-Trm[0][1],-Trm[1][0],Trm[1][1],0,0);
//apply it to the shape first so we can align
final 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],-Trm[0][1],-Trm[1][0],Trm[1][1],-nx,-ny);
//Create the affine operation.
//ColorSpaces.hints causes single lines to vanish);
final 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){
/*
* Hack for general-Sept2013/Page-1-BAW-A380-PDP.pdf buttons
* the filter performed on images here seems to break on images with a height of 1px
* which are being sheared. Resulting in a black image.
*/
if( h == 1 && Trm[0][0] == 0 && Trm[0][1] > 0 && Trm[1][0] < 0 && Trm[1][1] == 0){
final BufferedImage newImage = new BufferedImage(h,w,BufferedImage.TYPE_INT_ARGB);
for(int i = 0; i < w; i++){
final int col = current_image.getRGB(i, 0);
newImage.setRGB(0, (w - 1) - i, col);
}
current_image = newImage;
}else {
current_image = invert.filter(current_image, null);
}
}
}
}
/**workout the transformation as 1 or 2 transformations*/
private void createMatrices(){
final int w = (int) (current_image.getWidth()/scaling); //raw width
final int h = (int) (current_image.getHeight()/scaling); //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,x2,y1,y2;
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;
}
}
}
/*
* correct if image reversed on horizontal axis
*/
if(Trm1[2][0]<0 && Trm1[0][0]>0 && CTM[0][0]<0){
Trm1[2][0]=0;
Trm1[0][0]=-1f;
}
/*
* correct if image reversed on vertical axis
*/
if(Trm1[2][1]<0 && Trm1[1][1]>0 && CTM[1][1]<0 && CTM[0][0]<0){
Trm1[2][1]=0;
Trm1[1][1]=-1f;
}
}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 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_h;
}
if ( CTM[0][0]< 0) {
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)
*/
public final int getImageY() {
return i_y;
}
//////////////////////////////////////////////////////////////////////////
/**
* get image
*/
public final BufferedImage getImage() {
return current_image;
}
//////////////////////////////////////////////////////////////////////////
/**
* get width of image
*/
public final int getImageW() {
return i_w;
}
//////////////////////////////////////////////////////////////////////////
/**
* get height of image
*/
public final int getImageH() {
return i_h;
}
//////////////////////////////////////////////////////////////////////////
/**
* get X of image (x,y is top left)
*/
public final int getImageX() {
return i_x;
}
/**
* clip the image
*/
private void clipImage(final Area final_clip, final Area unscaled_clip, final int segCount) {
if(debug) {
System.out.println("[clip image] segCount=" + segCount);
}
final double shape_x = unscaled_clip.getBounds2D().getX();
final double shape_y = unscaled_clip.getBounds2D().getY();
final int image_w = current_image.getWidth();
final int image_h = current_image.getHeight();
//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();
if(debug) {
System.out.println("[clip image] raw clip size==" + x + ' ' + y + ' ' + w + ' ' + h + " image size=" + image_w + ' ' + image_h);
}
//System.out.println(x+" "+y+" "+w+" "+h+" "+current_image.getWidth()+" "+current_image.getHeight());
//if(BaseDisplay.isRectangle(final_clip)<7 && Math.abs(final_clip.getBounds().getWidth()-current_image.getWidth())<=1 && Math.abs(final_clip.getBounds().getHeight()-current_image.getHeight())<=1){
/*
* if not rectangle create inverse of clip and paint on to add transparency
*/
if(segCount>5){
if(debug) {
System.out.println("[clip image] create inverse of clip");
}
//turn image upside down
final AffineTransform image_at =new AffineTransform();
image_at.scale(1,-1);
image_at.translate(0,-current_image.getHeight());
final AffineTransformOp invert= new AffineTransformOp(image_at, ColorSpaces.hints);
current_image = invert.filter(current_image,null);
final Area inverseClip =new Area(new Rectangle(0,0,image_w,image_h));
inverseClip.exclusiveOr(final_clip);
current_image = ColorSpaceConvertor.convertToARGB(current_image);//make sure has opacity
final Graphics2D image_g2 = current_image.createGraphics(); //g2 of canvas
image_g2.setComposite(AlphaComposite.Clear);
image_g2.fill(inverseClip);
//and invert again
final AffineTransform image_at2 =new AffineTransform();
image_at2.scale(1,-1);
image_at2.translate(0,-current_image.getHeight());
final AffineTransformOp invert3= new AffineTransformOp(image_at2, ColorSpaces.hints);
current_image = invert3.filter(current_image,null);
}
//get image (now clipped )
//check for rounding errors
//if (y < 0 && 1==2) { //causes issues in some HTML files so commented out to see impact (see case 17246) general-May2014/Pages from SVA-E170-SOPM.pdf
// h = h - y;
// y = 0;
// }else
{
//do not do if image inverted
if(CTM[1][1]<0 && pageRotation==0 && CTM[0][0]>0 && CTM[1][0]==0 && CTM[0][1]==0){
//needs to be ignored
///Users/markee/PDFdata/test_data/sample_pdfs_html/general-May2014/17461.pdf
}else{
y=image_h-h-y;
}
//allow for fp error
if(y<0){
y=0;
}
}
if (x < 0) {
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 smaller with clip
if(h<1 || w<1){ //ignore if not wide/high enough
}else if(x==0 && y==0 && w==current_image.getWidth() && h==current_image.getHeight()){
//dont bother if no change
}else if(CTM[1][1]==0 && pageRotation==0 && CTM[0][0]==0 && CTM[1][0]<0 && CTM[0][1]>0){
//ignore for moment
///Users/markee/PDFdata/test_data/sample_pdfs_html/general-May2014/17733.pdf
}else{
try {
current_image = current_image.getSubimage(x, y, w, h);
if(debug) {
System.out.println("[clip image] reduce size x,y,w,h=" + x + ", " + y + ", " + w + ", " + h);
}
} catch (final Exception e) {
LogWriter.writeLog("Exception " + e + " extracting clipped image with values x="+x+" y="+y+" w="+w+" h="+h+" from image "+current_image);
}catch(final Error err){
LogWriter.writeLog("Exception " + err + " extracting clipped image with values x=" + x + " y=" + y + " w=" + w + " h=" + h + " from image " + current_image);
}
}
//work out new co-ords from shape and current
final double x1;
final double 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;
}
private Area getUnscaledClip(final Area final_clip) {
double dx=-(CTM[2][0]),dy= -CTM[2][1];
if(CTM[1][0]<0){
dx -= CTM[1][0] ;
}
if((CTM[0][0]<0)&&(CTM[1][0]>=0)){
dx -= CTM[1][0] ;
}
if(CTM[0][1]<0){
dy -= CTM[0][1];
}
if(CTM[1][1]<0){
if(CTM[0][1]>0){
dy -= CTM[0][1];
}else if(CTM[1][1]<0){
dy -= CTM[1][1];
}
}
final AffineTransform align_clip = new AffineTransform();
align_clip.translate(dx,dy );
final_clip.transform(align_clip);
final AffineTransform invert2=new AffineTransform(1/Trm2[0][0],0,0,1/Trm2[1][1],0,0);
final_clip.transform(invert2);
//fix for 'mirror' image on Mac
final int dxx = (int) final_clip.getBounds().getX();
if(dxx<0){
final_clip.transform(AffineTransform.getTranslateInstance(-dxx,0));
}
return final_clip;
}
}