iText binary transparency bug - itext

I came accross the following problem embedding a transparent java.awt.Image into a pdf with iText (tried with 2.1.7 und 5.5.9).
I suppose iText does not handle binary transparent images correctly in this special case when converting them from ARGB Image to iText Image.
When the Image provided only contains 100% and 0% transparent pixels (i.e. binary transparency), and all the pixels are black (the opaqe and transparent ones - i.e. all pixel of the image have color=black but alpha value of 0% or 100%), the transparent pixel color value is internally detected as beeing black (this is wrong in my opinion), which leads to an invisble image in the generated pdf.
Test-Case:
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfWriter;
public class BinaryTransparencyBug {
private static Image bkgnd;
public static void main(String[] args) throws Exception {
bkgnd = Image.getInstance(new URL("http://gitlab.itextsupport.com/itext/sandbox/raw/master/resources/images/berlin2013.jpg"));
bkgnd.scaleAbsolute(PageSize.A4);
bkgnd.setAbsolutePosition(0, 0);
Document document = new Document();
File file = new File("target/binary_transparency_bug.pdf");
FileOutputStream outputStream = new FileOutputStream(file);
PdfWriter writer = PdfWriter.getInstance(document, outputStream);
document.open();
addBackground(writer);
document.add(new Paragraph("Binary transparency bug test case"));
document.add(new Paragraph("OK: Visible image (opaque pixels are red, non opaque pixels are black)"));
document.add(com.itextpdf.text.Image.getInstance(createBinaryTransparentAWTImage(Color.red,false,null), null));
document.newPage();
addBackground(writer);
document.add(new Paragraph("Suspected bug: invisible image (both opaque an non opaque pixels have the same color)"));
document.add(com.itextpdf.text.Image.getInstance(createBinaryTransparentAWTImage(Color.black,false,null), null));
document.newPage();
addBackground(writer);
document.add(new Paragraph("Analysis: Aliasing makes the problem disappear, because this way the image is not binary transparent any more"));
document.add(com.itextpdf.text.Image.getInstance(createBinaryTransparentAWTImage(Color.black,true,null), null));
document.newPage();
addBackground(writer);
document.add(new Paragraph("Analysis: Setting the color of the transparent pixels to anything but black makes the problem go away, too"));
document.add(com.itextpdf.text.Image.getInstance(createBinaryTransparentAWTImage(Color.black,false,Color.red), null));
document.close();
}
private static void addBackground(PdfWriter writer)
throws BadElementException, MalformedURLException, IOException, DocumentException {
PdfContentByte canvas = writer.getDirectContentUnder();
canvas.saveState();
canvas.addImage(bkgnd);
canvas.restoreState();
}
// Create an ARGB AWT Image that has only 100% transparent and 0%
// transparent pixels.
// All 100% opaque pixels have the provided color "color"
// All transparent pixels have the Color "backgroundColor"
public static BufferedImage createBinaryTransparentAWTImage(Color color, boolean alias, Color backgroundColor) {
Dimension size = new Dimension(200, 200);
BufferedImage awtimg = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = awtimg.createGraphics();
if (backgroundColor!=null)
{
//Usually it doen't make much sense to set the color of transparent pixels...
//but in this case it changes the behavior of com.itextpdf.text.Image.getInstance fundamentally!
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0f));
g2d.setColor(backgroundColor);
g2d.fillRect(0, 0, size.width, size.height);
}
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
g2d.setColor(color);
if (alias)
{
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
}
BasicStroke bs = new BasicStroke(2);
g2d.setStroke(bs);
for (int i = 0; i < 5; i++) {
g2d.drawLine((size.width + 2) / 4 * i, 0, (size.width + 2) / 4 * i, size.height - 1);
g2d.drawLine(0, (size.height + 2) / 4 * i, size.width - 1, (size.height + 2) / 4 * i);
}
return awtimg;
}
}

Here is my proposal for the fix of the problem:
in Image.getInstance(AWTImage,color,forcebw):
in the case forceBW=false and color=null:
for (int j = 0; j < size; j++) {
byte alpha = smask[j] = (byte) (pixels[j] >> 24 & 0xff);
/* bugfix by Chris Nokleberg */
if (!shades) {
if (alpha != 0 && alpha != -1) {
//as soon as there is any pixel with alpha not 0% or 100%
//switch to smask
shades = true;
} else if (transparency == null) {
//in binary transparency mode, determine the transparentPixel Color to be the
//value of the first Pixel we find with 100% transparency
if (alpha == 0) {
transparentPixel = pixels[j] & 0xffffff;
transparency = new int[6];
transparency[0] = transparency[1] = transparentPixel >> 16 & 0xff;
transparency[2] = transparency[3] = transparentPixel >> 8 & 0xff;
transparency[4] = transparency[5] = transparentPixel & 0xff;
// vvv--- added by mkl
// Check whether this value for transparent pixels
// has already been used for a non-transparent one
// before this position
for (int jj = 0; jj < j; jj++)
{
if ((pixels[jj] & 0xffffff) == transparentPixel)
{
// found a prior use of the transparentPixel color
// and, therefore, cannot make use of this color
// for transparency; we could still use an image
// mask but for simplicity let's use a soft mask
// which already is implemented here
shades = true;
break;
}
}
// ^^^--- added by mkl
}
} else if (((pixels[j] & 0xffffff) != transparentPixel) && (alpha==0)) {
//TB: The above if seems incorrect to me. (EDIT: it was if ((pixels[j] & 0xffffff) != transparentPixel)
//As soon as we find any pixel that has differnt color from
//transparentPixel-Color and alpha 0% or 100%
//switch of binary transparency mode.
//IMHO this should only be done if alpha==0!
//so the if clause should be
//((pixels[j] & 0xffffff) != transparentPixel) && (alpha==0)
shades = true;
}
//TB: Proposed fix:
else if ((pixels[j] & 0xffffff) == transparentPixel && alpha!=0) {
//switch of binary transparency mode, if we find any pixel with the transparentPixel-Color,
//but which is not transparent
shades = true;
}
}
...
}

Related

Changing the color of a gameobject shows as white not the colour requested

I have a set of gameobjects (simple cubes). I can set their initial colour when instantiating them. However when I try and change the colour by code, the object in the game view and inspector show as white, but in the colour picker show the correct colour!
There is a single directional light (the default one).
IEnumerator ColourChange()
{
Color targetColour = new Color(Random.Range(0, 255), Random.Range(0, 255), Random.Range(0, 255));
Debug.Log("color = " + targetColour);
for (int x = 0; x < CreateCubeGrid.GRIDSIZE; x++) {
for (int z = 0; z < CreateCubeGrid.GRIDSIZE; z++) {
CreateCubeGrid.cubeGrid[x,z].GetComponent<Renderer>().material.color = targetColour;
}
yield return new WaitForSeconds (0.05f);
}
}
Colours are 0 to 1 not 0 to 255.
Use Color32 if you want to use 0-255 values.
Color32 Documentation
In order to change the material's color, you must tell it exactly what color you're trying to change, by using Shader.Find("_YourColor") (Emission, Albedo, etc).
An approach that should work for materials using Standard Shader can be seen below:
private void ChangeColor(Color color)
{
//Fetch the Renderer from the GameObject.
Renderer rend = GetComponent<Renderer>();
//Find and set the main Color ("_Color") of the Material to the new one
rend.material.shader = Shader.Find("_Color");
rend.material.SetColor("_Color", color);
}
You can read more about changing a Material's color at Unity's Documentation.

iText Background Opacity

I want to overlay a text with semi-transparent background over an existing text using iText 7. Setting the background opacity for a text element doesn't seem to work (line 1), I can only set it for the whole paragraph (line 2):
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Text;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.property.VerticalAlignment;
import java.io.IOException;
public class TextBackgroundOpacityTest {
public static void main(String[] args) throws IOException {
try (Document doc = new Document( new PdfDocument(new PdfWriter("TextBackgroundOpacityTest.pdf")))) {
doc.add(new Paragraph(new String(new char[130]).replace("\0", "A")));
// opacity doesn't work for text element
doc.showTextAligned(new Paragraph(new Text("missing background transparency").setBackgroundColor(ColorConstants.WHITE, .8f)), 500, 805, 0, TextAlignment.RIGHT, VerticalAlignment.TOP, 0);
// opacity for the whole paragraph works, but this is not what I want
doc.showTextAligned(new Paragraph("whole pharagraph background transparancy").setBackgroundColor(ColorConstants.WHITE, .8f), 500, 785, 0, TextAlignment.RIGHT, VerticalAlignment.TOP, 0);
}
}
}
How can I overlay a text with a semi-transparent background as show in line 2, but just for the overlayed text, not the whole paragraph? Desired output:
To work around the solution you can use custom renderers. If you look at the BlockRenderer#drawBackground which is called in case you set transparent background to a paragraph you can see the following lines there:
TransparentColor backgroundColor = new TransparentColor(background.getColor(), background.getOpacity());
drawContext.getCanvas().saveState().setFillColor(backgroundColor.getColor());
backgroundColor.applyFillTransparency(drawContext.getCanvas());
TextRenderer, however, has its own implementation and does not respect transparent background. But we can customize the renderer implementation. We'll need to copy-paste quite a bit of code from the current TextRenderer implementation, but the good news is that we don't need to change a lot of code. Just insert two lines in the right place:
TransparentColor backgroundColor = new TransparentColor(background.getColor(), background.getOpacity());
backgroundColor.applyFillTransparency(drawContext.getCanvas());
Overall we get the following implementation:
private static class TextRendererWithBackgroundOpacity extends TextRenderer {
public TextRendererWithBackgroundOpacity(Text textElement) {
super(textElement);
}
#Override
public void drawBackground(DrawContext drawContext) {
Background background = this.<Background>getProperty(Property.BACKGROUND);
Float textRise = this.getPropertyAsFloat(Property.TEXT_RISE);
Rectangle bBox = getOccupiedAreaBBox();
Rectangle backgroundArea = applyMargins(bBox, false);
float bottomBBoxY = backgroundArea.getY();
float leftBBoxX = backgroundArea.getX();
if (background != null) {
boolean isTagged = drawContext.isTaggingEnabled();
PdfCanvas canvas = drawContext.getCanvas();
if (isTagged) {
canvas.openTag(new CanvasArtifact());
}
boolean backgroundAreaIsClipped = clipBackgroundArea(drawContext, backgroundArea);
canvas.saveState().setFillColor(background.getColor());
TransparentColor backgroundColor = new TransparentColor(background.getColor(), background.getOpacity());
backgroundColor.applyFillTransparency(drawContext.getCanvas());
canvas.rectangle(leftBBoxX - background.getExtraLeft(), bottomBBoxY + (float) textRise - background.getExtraBottom(),
backgroundArea.getWidth() + background.getExtraLeft() + background.getExtraRight(),
backgroundArea.getHeight() - (float) textRise + background.getExtraTop() + background.getExtraBottom());
canvas.fill().restoreState();
if (backgroundAreaIsClipped) {
drawContext.getCanvas().restoreState();
}
if (isTagged) {
canvas.closeTag();
}
}
}
#Override
public IRenderer getNextRenderer() {
return new TextRendererWithBackgroundOpacity((Text)modelElement);
}
}
To make Text element use the custom renderer implementation just call setNextRenderer method:
Text customTextElement = new Text("missing background transparency");
customTextElement.setNextRenderer(new TextRendererWithBackgroundOpacity(customTextElement));
By the way you are very welcome to file the fix as a pull request to iText (please follow the contribution guidelines though). The repository is located at https://github.com/itext/itext7

How to modify a Texture pixels from a compute shader in unity?

I stumbled upon a strange problem in vuforia.When i request a camera image using CameraDevice.GetCameraImage(mypixelformat), the image returned is both flipped sideways and rotated 180 deg. Because of this, to obtain a normal image i have to first rotate the image and then flip it sideways.The approach i am using is simply iterating over pixels of the image and modifying them.This approach is very poor performance wise.Below is the code:
Texture2D image;
CameraDevice cameraDevice = Vuforia.CameraDevice.Instance;
Vuforia.Image vufImage = cameraDevice.GetCameraImage(pixelFormat);
image = new Texture2D(vufImage.Width, vufImage.Height);
vufImage.CopyToTexture(image);
Color32[] colors = image.GetPixels32();
System.Array.Reverse(colors, 0, colors.Length); //rotate 180deg
image.SetPixels32(colors); //apply rotation
image = FlipTexture(image); //flip sideways
//***** THE FLIP TEXTURE METHOD *******//
private Texture2D FlipTexture(Texture2D original, bool upSideDown = false)
{
Texture2D flipped = new Texture2D(original.width, original.height);
int width = original.width;
int height = original.height;
for (int col = 0; col < width; col++)
{
for (int row = 0; row < height; row++)
{
if (upSideDown)
{
flipped.SetPixel(row, (width - 1) - col, original.GetPixel(row, col));
}
else
{
flipped.SetPixel((width - 1) - col, row, original.GetPixel(col, row));
}
}
}
flipped.Apply();
return flipped;
}
To improve the performance i want to somehow schedule these pixel operations on the GPU, i have heard that a compute shader can be used, but i have no idea where to start.Can someone please help me write the same operations in a compute shader so that the GPU can handle them, Thankyou!.
The whole compute shader are new for me too, but i took the occasion to research it a little bit for myself too. The following works for flipping a texture vertically (rotating and flipping horizontally should be just a vertical flip).
Someone might have a more elaborate solution for you, but maybe this is enough to get you started.
The Compute shader code:
#pragma kernel CSMain
// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture
RWTexture2D<float4> Result;
Texture2D<float4> ImageInput;
float2 flip;
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
flip = float2(512 , 1024) - id.xy ;
Result[id.xy] = float4(ImageInput[flip].x, ImageInput[flip].y, ImageInput[flip].z, 1.0);
}
and called from any script:
public void FlipImage()
{
int kernelHandle = shader.FindKernel("CSMain");
RenderTexture tex = new RenderTexture(512, 1024, 24);
tex.enableRandomWrite = true;
tex.Create();
shader.SetTexture(kernelHandle, "Result", tex);
shader.SetTexture(kernelHandle, "ImageInput", myTexture);
shader.Dispatch(kernelHandle, 512/8 , 1024 / 8, 1);
RenderTexture.active = tex;
result.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0);
result.Apply();
}
This takes an input Texture2D, flips it in the shader, applies it to a RenderTexture and to a Texture2D, whatever you need.
Note that the image sizes are hardcoded in my instance and should be replaced by whatever size you need. (for within the shader use shader.SetInt(); )

Underlining with pdfnet results in different line thickness

The code that i use for underlining a selection of text. I begin calling the addUnderline() method, the other methods are helper methods.
private pdftron.SDF.Obj CreateUnderlineAppearance(pdftron.PDF.Rect bbox)
{
ElementBuilder builder = new ElementBuilder();
ElementWriter writer = new ElementWriter();
builder.PathBegin();
builder.MoveTo(bbox.x1, bbox.y1);
builder.LineTo(bbox.x2, bbox.y1);
Element line = builder.PathEnd();
//Set color attributes for the line...
line.SetPathFill(false);
line.SetPathStroke(true);
GState gs = line.GetGState();
gs.SetStrokeColorSpace(ColorSpace.CreateDeviceRGB());
gs.SetStrokeColor(new ColorPt(0, 0, 0)); // black
gs.SetLineWidth(2);
writer.Begin(m_document);
writer.WriteElement(line);
pdftron.SDF.Obj stm = writer.End();
builder.Dispose();
writer.Dispose();
// Set the bounding box
stm.PutRect("BBox", bbox.x1, bbox.y1, bbox.x2, bbox.y2);
stm.PutName("Subtype", "Form");
return stm;
}
public Annot CreateUnderlineAnnot(pdftron.PDF.Rect rect)
{
Annot underlineAnnot = Annot.Create(m_document, Annot.Type.e_Underline, rect);
underlineAnnot.SetAppearance(CreateUnderlineAppearance(rect));
return underlineAnnot;
}
public void AddUnderline()
{
if (m_document != null)
{
PDFViewCtrl.Selection selection = m_pdfViewer.GetSelection();
int pageNumber = selection.GetPageNum();
double[] quads = selection.GetQuads();
int numQuads = quads.Length / 8;
if (quads.Length % 8 == 0) //must have at least 8 points to be valid
{
Console.WriteLine("GetRectsFromQuads - numQuads: " + numQuads.ToString());
for (int i = 0; i < numQuads; i++)
{
Rect selectionRect = GetSelectionRect(ref quads, i);
//Console.WriteLine("GetRectsFromQuads - aRect: " + rectX1.ToString() + " | " + rectY1.ToString() + " | " + rectX2.ToString() + " | " + rectY2.ToString());
Annot underlineAnnot = CreateUnderlineAnnot(selectionRect);
m_pdfViewer.AddUnderlineAnnotationToPage(underlineAnnot, pageNumber);
//m_pdfViewer.Refresh(); --> to see how this algorithm works when debugging
}
m_pdfViewer.RefreshAnnotations();
}
}
}
You can see in the image if you look closely that some lines are thicker or thinner than others. Is this fixable? by the way, when i zoom in/out the problem is gone...
You need to set the following on your pdf view control:
PDFViewCtrl.SetThinLineAdjustment(true, true);
That will remove the aliasing on the lines, and mean all lines that are 1.5px are 1px, and so on. See here: https://www.pdftron.com/pdfnet/mobile/docs/WinRT/html/M_pdftron_PDF_PDFViewCtrl_SetThinLineAdjustment.htm

iTextSharp - Some pages not stamped as expected

I'm using iTextSharp 5.0.6 to read an existing PDF, iterate each page stamping text on each, and then writing out the newly stamped PDF. The issue I'm faced with is that this isn't working 100% of the time. For some PDFs every page is stamped as expected, for others most pages are stamped while some are not. Seems as if there's potentially an issue where the stamper's GetOverContent() is not returning the top-most layer, but that's just an assumption. Has anyone had a similar issue?
using iTextSharp.text;
using iTextSharp.text.pdf;
const string WATERMARK_TEXT = "John Doe";
static void Main(string[] args)
{
string masterPdf = "master.pdf";
string pdfToCreate = "watermark.pdf";
byte[] bytes = StampPDF(masterPdf);
using (FileStream stream = new FileStream(pdfToCreate, FileMode.Create))
{
stream.Write(bytes, 0, bytes.Length);
}
}
static byte[] StampPDF(string PdfPath)
{
using (MemoryStream memoryStream = new MemoryStream())
{
PdfReader reader = new PdfReader(PdfPath);
int pageCount = reader.NumberOfPages;
PdfStamper stamper = new PdfStamper(reader, memoryStream);
float fontSize = 9;
float textAngle = 0f;
BaseFont font = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.WINANSI, BaseFont.EMBEDDED);
BaseColor backgroundColor = new BaseColor(0, 0, 0);
BaseColor fontColor = new BaseColor(255, 255, 255);
float padding = 2f;
float fontWidth = font.GetWidthPoint(WATERMARK_TEXT, fontSize);
iTextSharp.text.Rectangle pageSize;
PdfContentByte pageContents;
for (int i = 1; i <= pageCount; i++)
{
pageSize = reader.GetPageSize(i);
pageContents = stamper.GetOverContent(i);
//draw a rectangle
pageContents.SetColorFill(backgroundColor);
pageContents.MoveTo(pageSize.Width - (fontWidth + padding), 0f);
pageContents.LineTo(pageSize.Width, 0f);
pageContents.LineTo(pageSize.Width, 14f);
pageContents.LineTo(pageSize.Width - (fontWidth + padding), 14f);
pageContents.Fill();
//drop our watermark on top of the rectangle we just created
pageContents.BeginText();
pageContents.SetColorFill(fontColor);
pageContents.SetFontAndSize(font, fontSize);
pageContents.ShowTextAligned(PdfContentByte.ALIGN_LEFT, WATERMARK_TEXT, pageSize.Width - fontWidth, 4, textAngle);
pageContents.EndText();
}
stamper.Close();
reader.Close();
return memoryStream.ToArray();
}
}
For those that may encounter the same problem the key is inspecting the CropBox. Since the dimensions of a PDF's CropBox may be less than that of its PageSize you need to conditionally use one or the other. So, based on the code sample above the for loop would be altered as so:
for (int i = 1; i <= pageCount; i++)
{
mediaBox = reader.GetPageSize(i);
cropBox = reader.GetCropBox(i);
overContent = stamper.GetOverContent(i);
if (cropBox != null && (cropBox.Width < mediaBox.Width || cropBox.Height < cropBox.Height))
mediaBox = cropBox;
//draw a rectangle
overContent.SetColorFill(backgroundColor);
overContent.MoveTo(mediaBox.Right - (fontWidth + fontPadding), mediaBox.Bottom);
overContent.LineTo(mediaBox.Right, mediaBox.Bottom);
overContent.LineTo(mediaBox.Right, mediaBox.Bottom + rectangleHeight);
overContent.LineTo(mediaBox.Right - (fontWidth + fontPadding), mediaBox.Bottom + rectangleHeight);
overContent.ClosePathFillStroke();
//drop our watermark on top of the rectangle we just created
overContent.BeginText();
overContent.SetColorFill(fontColor);
overContent.SetFontAndSize(font, fontSize);
overContent.ShowTextAligned(PdfContentByte.ALIGN_LEFT, WATERMARK_TEXT, mediaBox.Right - fontWidth, mediaBox.Bottom + (rectangleHeight - fontSize), textAngle);
overContent.EndText();
}
You've made two mistakes:
You're assuming that the pages aren't rotated, but they can be: 90, 180, 270. Note that I've never seen a 180 page, but its legal. When drawing to a rotated page, you have to take that rotation into account when drawing on it. Fun with transformation matrices.
You're assuming that the page's (unrotated) lower left corner is 0,0. You're basing your measurements on the page's width and height (close), but aren't adjusting for any offset in that bottom left corner.
There are three ways to do a landscape page:
11"x8.5"
8.5"x11" # 90 degrees rotation
8.5"x11" # 270 degrees rotation
Technically, a 4th way is to build an 11x8.5 # 180, but anyone writing such code should be Punished. A lot.
There are various SO questions floating about that give details on how to deal with page rotation. Going by your code, I'd say you'll figure out the llx,lly thing pretty quickly.