Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
package com.tom_roush.pdfbox.pdmodel.graphics.shading;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.util.Log;

import androidx.test.platform.app.InstrumentationRegistry;

import com.tom_roush.harmony.javax.imageio.stream.MemoryCacheImageOutputStream;
import com.tom_roush.pdfbox.android.PDFBoxResourceLoader;
import com.tom_roush.pdfbox.cos.COSArray;
import com.tom_roush.pdfbox.cos.COSDictionary;
import com.tom_roush.pdfbox.cos.COSFloat;
import com.tom_roush.pdfbox.cos.COSInteger;
import com.tom_roush.pdfbox.cos.COSName;
import com.tom_roush.pdfbox.cos.COSStream;
import com.tom_roush.pdfbox.pdmodel.PDDocument;
import com.tom_roush.pdfbox.pdmodel.PDPage;
import com.tom_roush.pdfbox.pdmodel.PDPageContentStream;
import com.tom_roush.pdfbox.pdmodel.common.function.PDFunctionType2;
import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import com.tom_roush.pdfbox.rendering.ImageType;
import com.tom_roush.pdfbox.rendering.PDFRenderer;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

public class ShadingEnd2EndTest {

private Context testContext;

@Before
public void setUp() {
testContext = InstrumentationRegistry.getInstrumentation().getContext();
PDFBoxResourceLoader.init(testContext);
}

@Test
public void TestShading2() {
try {
Bitmap bitmap = getShadingBitmap(2);
int height = bitmap.getHeight();
Assert.assertEquals("(5, -5) should be white", Color.WHITE, bitmap.getPixel(5, height - 5));
int cdiff = calcColorDiff(Color.RED, bitmap.getPixel(13, height - 13));
Assert.assertTrue("(13, -13) should be red, diff:" + cdiff, cdiff < 15);
cdiff = calcColorDiff(Color.GREEN, bitmap.getPixel(95, height - 95));
Assert.assertTrue("(95, -95) should be green, diff:" + cdiff, cdiff < 35);
Assert.assertEquals("(110, -110) should be white", Color.WHITE, bitmap.getPixel(110, height - 110));
}
catch (IOException e){
Assert.fail("Exception: " + e.getMessage());
}
}

@Test
public void TestShading3(){
try {
Bitmap bitmap = getShadingBitmap(3);
int cdiff = calcColorDiff(Color.rgb(10, 10, 10), bitmap.getPixel(100, 394));
Assert.assertTrue("(100, 394) should be black, diff: " + cdiff, cdiff < 20);

cdiff = calcColorDiff(Color.rgb(128, 128, 128), bitmap.getPixel(102, 367));
Assert.assertTrue("(102, 367) should be gray, diff: " + cdiff, cdiff < 20);

cdiff = calcColorDiff(Color.rgb(255, 255, 255), bitmap.getPixel(102, 340));
Assert.assertTrue("(102, 340) should be white, diff: " + cdiff, cdiff < 20);

cdiff = calcColorDiff(Color.rgb(153, 153, 153), bitmap.getPixel(130, 390));
Assert.assertTrue("(130, 390) should be gray, diff: " + cdiff, cdiff < 20);
}
catch (IOException e){
Assert.fail("Exception: " + e.getMessage());
}
}

@Test
public void TestShading4(){
try {
Bitmap bitmap = getShadingBitmap(4);
int height = bitmap.getHeight();
int cdiff = calcColorDiff(Color.RED, bitmap.getPixel(5, height - 2));
Assert.assertTrue("(5, -2) should be red, diff: " + cdiff, cdiff < 20);

cdiff = calcColorDiff(Color.GREEN, bitmap.getPixel(100, height - 97));
Assert.assertTrue("(100, -97) should be green, diff: " + cdiff, cdiff < 25);

cdiff = calcColorDiff(Color.BLUE, bitmap.getPixel(195, height - 2));
Assert.assertTrue("(195, -2) should be green, diff: " + cdiff, cdiff < 25);

cdiff = calcColorDiff(Color.WHITE, bitmap.getPixel(5, height - 8));
Assert.assertTrue("(5, -8) should be white, diff: " + cdiff, cdiff < 20);

cdiff = calcColorDiff(Color.WHITE, bitmap.getPixel(195, height - 10));
Assert.assertTrue("(195, -10) should be white, diff: " + cdiff, cdiff < 20);
}
catch (IOException e){
Assert.fail("Exception: " + e.getMessage());
}
}

private static int calcColorDiff(int a, int cb) {
int r = Color.red(cb);
int g = Color.green(cb);
int b = Color.blue(cb);
Log.i("Test", String.format("(%d, %d, %d)", r, g, b));
return Math.abs(Color.red(a) - r) +
Math.abs(Color.green(a) - g) +
Math.abs(Color.blue(a) - b);
}

public static Bitmap getShadingBitmap(int stype) throws IOException {
PDShading shading;
if(stype == 2){
shading = createShading2();
}
else if(stype == 3){
shading = createShading3();
}
else if(stype == 4){
shading = createShading4();
}
else{
throw new UnsupportedOperationException("shading type not support:" + stype);
}
PDDocument document = new PDDocument();
PDPage page = new PDPage();
document.addPage(page);
try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, false))
{
contentStream.shadingFill(shading);
}
PDFRenderer renderer = new PDFRenderer(document);
// Render the image to an RGB Bitmap
return renderer.renderImage(0, 1, ImageType.RGB);
}


private static PDShadingType4 createShading4() throws IOException {
// See PDF 32000 specification,
// 8.7.4.5.5 Type 4 Shadings (Free-Form Gouraud-Shaded Triangle Meshes)
PDShadingType4 gouraudShading = new PDShadingType4(new COSStream());
gouraudShading.setShadingType(PDShading.SHADING_TYPE4);
// we use multiple of 8, so that no padding is needed
gouraudShading.setBitsPerFlag(8);
gouraudShading.setBitsPerCoordinate(16);
gouraudShading.setBitsPerComponent(8);
COSArray decodeArray = new COSArray();
// coordinates x y map 16 bits 0..FFFF to 0..FFFF to make your life easy
// so no calculation is needed, but you can only use integer coordinates
// for real numbers, you'll need smaller bounds, e.g. 0xFFFF / 0xA = 0x1999
// would allow 1 point decimal result coordinate.
// See in PDF specification: 8.9.5.2 Decode Arrays
decodeArray.add(COSInteger.ZERO);
decodeArray.add(COSInteger.get(0xFFFF));
decodeArray.add(COSInteger.ZERO);
decodeArray.add(COSInteger.get(0xFFFF));
// colors r g b map 8 bits from 0..FF to 0..1
decodeArray.add(COSInteger.ZERO);
decodeArray.add(COSInteger.ONE);
decodeArray.add(COSInteger.ZERO);
decodeArray.add(COSInteger.ONE);
decodeArray.add(COSInteger.ZERO);
decodeArray.add(COSInteger.ONE);
gouraudShading.setDecodeValues(decodeArray);
gouraudShading.setColorSpace(PDDeviceRGB.INSTANCE);

// Function is not required for type 4 shadings and not really useful,
// because if a function would be used, each edge "color" of a triangle would be one value,
// which would then transformed into n color components by the function so it is
// difficult to get 3 "extremes".

OutputStream os = ((COSStream) gouraudShading.getCOSObject()).createOutputStream();
MemoryCacheImageOutputStream mcos = new MemoryCacheImageOutputStream(os);

// Vertex 1, starts with flag1
// (flags always 0 for vertices of start triangle)
mcos.writeByte(0);
// x1 y1 (left corner)
mcos.writeShort(0);
mcos.writeShort(0);
// r1 g1 b1 (red)
mcos.writeByte(0xFF);
mcos.writeByte(0);
mcos.writeByte(0);

// Vertex 2, starts with flag2
mcos.writeByte(0);
// x2 y2 (top corner)
mcos.writeShort(100);
mcos.writeShort(100);
// r2 g2 b2 (green)
mcos.writeByte(0);
mcos.writeByte(0xFF);
mcos.writeByte(0);

// Vertex 3, starts with flag3
mcos.writeByte(0);
// x3 y3 (right corner)
mcos.writeShort(200);
mcos.writeShort(0);
// r3 g3 b3 (blue)
mcos.writeByte(0);
mcos.writeByte(0);
mcos.writeByte(0xFF);

mcos.close();
// outside stream MUST be closed as well, see javadoc of MemoryCacheImageOutputStream
os.close();
return gouraudShading;
}

private static PDShadingType3 createShading3() throws IOException {
// type 2 (exponential) function with attributes
COSDictionary fdict = new COSDictionary();
fdict.setInt(COSName.FUNCTION_TYPE, 2);
COSArray domain = new COSArray();
domain.add(COSInteger.get(0));
domain.add(COSInteger.get(1));
COSArray c0 = new COSArray();
c0.add(COSFloat.get("1"));
c0.add(COSFloat.get("1"));
c0.add(COSFloat.get("1"));
COSArray c1 = new COSArray();
c1.add(COSFloat.get("0"));
c1.add(COSFloat.get("0"));
c1.add(COSFloat.get("0"));
fdict.setItem(COSName.DOMAIN, domain);
fdict.setItem(COSName.C0, c0);
fdict.setItem(COSName.C1, c1);
fdict.setInt(COSName.N, 1);
PDFunctionType2 func = new PDFunctionType2(fdict);

// radial shading with attributes
PDShadingType3 radialShading = new PDShadingType3(new COSDictionary());
radialShading.setColorSpace(PDDeviceRGB.INSTANCE);
radialShading.setShadingType(PDShading.SHADING_TYPE3);
COSArray coords2 = new COSArray();
coords2.add(COSInteger.get(100));
coords2.add(COSInteger.get(400));
coords2.add(COSInteger.get(50)); // radius1
coords2.add(COSInteger.get(100));
coords2.add(COSInteger.get(400));
coords2.add(COSInteger.get(0)); // radius2
radialShading.setCoords(coords2);
radialShading.setFunction(func);

return radialShading;
}

private static PDShadingType2 createShading2() throws IOException {
int startColor = Color.RED;
int endColor = Color.GREEN;

COSDictionary fdict = new COSDictionary();

fdict.setInt(COSName.FUNCTION_TYPE, 2);

COSArray domain = new COSArray();
domain.add(COSInteger.get(0));
domain.add(COSInteger.get(1));

COSArray c0 = new COSArray();
c0.add(new COSFloat(Color.red(startColor) / 255f));
c0.add(new COSFloat(Color.green(startColor) / 255f));
c0.add(new COSFloat(Color.blue(startColor) / 255f));

COSArray c1 = new COSArray();
c1.add(new COSFloat(Color.red(endColor) / 255f));
c1.add(new COSFloat(Color.green(endColor) / 255f));
c1.add(new COSFloat(Color.blue(endColor) / 255f));

fdict.setItem(COSName.DOMAIN, domain);
fdict.setItem(COSName.C0, c0);
fdict.setItem(COSName.C1, c1);
fdict.setInt(COSName.N, 1);

PDFunctionType2 func = new PDFunctionType2(fdict);

PDShadingType2 axialShading = new PDShadingType2(new COSDictionary());

axialShading.setColorSpace(PDDeviceRGB.INSTANCE);
axialShading.setShadingType(PDShading.SHADING_TYPE2);

COSArray coords1 = new COSArray();
coords1.add(new COSFloat(10));
coords1.add(new COSFloat(10));
coords1.add(new COSFloat(100));
coords1.add(new COSFloat(100));

axialShading.setCoords(coords1);
axialShading.setFunction(func);

return axialShading;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/
package com.tom_roush.harmony.awt.geom;

import android.graphics.Path;
import android.graphics.PointF;

import java.io.IOException;
Expand Down Expand Up @@ -223,6 +224,54 @@ else if (m01 != 0.0 || m10 != 0.0)
return type;
}


/**
* Returns the x-scaling factor of this matrix. This is calculated from the scale and shear.
*
* @return The x-scaling factor.
*/
public double getScalingFactorX()
{
/**
* BM: if the trm is rotated, the calculation is a little more complicated
*
* The rotation matrix multiplied with the scaling matrix is:
* ( x 0 0) ( cos sin 0) ( x*cos x*sin 0)
* ( 0 y 0) * (-sin cos 0) = (-y*sin y*cos 0)
* ( 0 0 1) ( 0 0 1) ( 0 0 1)
*
* So, if you want to deduce x from the matrix you take
* M(0,0) = x*cos and M(0,1) = x*sin and use the theorem of Pythagoras
*
* sqrt(M(0,0)^2+M(0,1)^2) =
* sqrt(x2*cos2+x2*sin2) =
* sqrt(x2*(cos2+sin2)) = <- here is the trick cos2+sin2 is one
* sqrt(x2) =
* abs(x)
*/
if (m10 != 0.0f)
{
return (float) Math.sqrt(Math.pow(m00, 2) +
Math.pow(m10, 2));
}
return m00;
}

/**
* Returns the y-scaling factor of this matrix. This is calculated from the scale and shear.
*
* @return The y-scaling factor.
*/
public double getScalingFactorY()
{
if (m01 != 0.0f)
{
return (float) Math.sqrt(Math.pow(m01, 2) +
Math.pow(m11, 2));
}
return m11;
}

public double getScaleX()
{
return m00;
Expand Down Expand Up @@ -626,21 +675,16 @@ public void inverseTransform(double[] src, int srcOff, double[] dst, int dstOff,
}
}

// public Shape createTransformedShape(Shape src)
// {
// if (src == null)
// {
// return null;
// }
// if (src instanceof GeneralPath)
// {
// return ((GeneralPath) src).createTransformedShape(this);
// }
// PathIterator path = src.getPathIterator(this);
// GeneralPath dst = new GeneralPath(path.getWindingRule());
// dst.append(path, false);
// return dst;
// } TODO: PdfBox-Android
public Path createTransformedShape(Path src)
{
if (src == null)
{
return null;
}
Path outPath = new Path();
src.transform(this.toMatrix(), outPath);
return outPath;
}

@Override
public String toString()
Expand Down
Loading