PDFBox not returning the correct size of an image - pdfbox

I am new to PDFBox and am stuck at finding the height of an image in inches. After a couple of searches, this is the piece of code that I am working with:
PDResources resources = aPdPage.findResources();
graphicsState = new PDGraphicsState(aPdPage.findCropBox());
pageWidth = aPdPage.findCropBox().getWidth() / 72;
pageHeight = aPdPage.findCropBox().getHeight() / 72;
#SuppressWarnings("deprecation")
Map<String, PDXObjectImage> imageObjects = resources.getImages();
if (null == imageObjects || imageObjects.isEmpty())
return;
for (Map.Entry<String, PDXObjectImage> entryxObjects : imageObjects.entrySet()) {
PDXObjectImage image = entryxObjects.getValue();
// System.out.println("bits per component: " + image.getBitsPerComponent());
Matrix ctmNew = graphicsState.getCurrentTransformationMatrix();
float imageXScale = ctmNew.getXScale();
float imageYScale = ctmNew.getYScale();
System.out.println("position = " + ctmNew.getXPosition() + ", " + ctmNew.getYPosition());
// size in pixel
System.out.println("size = " + image.getWidth() + "px, " + image.getHeight() + "px");
// size in page units
System.out.println("size = " + imageXScale + "pu, " + imageYScale + "pu");
// size in inches
imageXScale /= 72;
imageYScale /= 72;
System.out.println("size = " + imageXScale + "in, " + imageYScale + "in");
// size in millimeter
imageXScale *= 25.4;
imageYScale *= 25.4;
System.out.println("size = " + imageXScale + "mm, " + imageYScale + "mm");
System.out.printf("dpi = %.0f dpi (X), %.0f dpi (Y) %n", image.getWidth() * 72 / ctmNew.getXScale(), image.getHeight() * 72 / ctmNew.getYScale());
}
But the value is not coming correctly in inches. The imageXScale value in pu is coming to be 0.1 always.
Any help would be appreciated.

First of all you need to know how bitmap images usually are used in PDFs:
In a PDF a page object has a collection of so called resources, among them bitmap image resources, font resources, ...
You can inspect these resources like you currently do:
PDResources resources = aPdPage.findResources();
#SuppressWarnings("deprecation")
Map<String, PDXObjectImage> imageObjects = resources.getImages();
if (null == imageObjects || imageObjects.isEmpty())
return;
for (Map.Entry<String, PDXObjectImage> entryxObjects : imageObjects.entrySet())
{
PDXObjectImage image = entryxObjects.getValue();
System.out.println("size = " + image.getWidth() + "px, " + image.getHeight() + "px");
}
But this only gives you the pixel dimension of the images as they are available in the page resources.
When such an resource is painted onto the page, the operation doing this actually first scales it down to a 1x1 unit square and paints this scaled down version.
The reason why you on screen and on paper have images of reasonable size, is that the way painting operators work in PDFs is influenced by the so called current graphics state. This graphics state contains information like the current fill color, line widths, etc... In particular it also contains the so called current transformation matrix which defines how everything some operation draws shall be stretched, rotated, skewed, translated, ... transformed.
The usual sequence of operations when drawing a bitmap image looks like this:
...
store a temporary copy of the current graphics state,
change the current transformation matrix by a scaling transformation which multiplies the x coordinate by the desired widths and the y coordinate by the desired height of the image to draw,
draw the image referenced in the resources, and
restore the current graphics state to the temporarily stored values,
...
Thus, to know the dimensions of the image on the page, you have to know the current transformation matrix as it is when the image drawing operation is executed.
Your code, on the other hand, uses the current transformation matrix from a freshly instantiated graphics state with all values at defaults. Thus, your code prints the false information on how the image is scaled on the page.
To get the correct information, you have to parse the sequence of operations executed for creating the document page.
This is exactly what the PDFBox PrintImageLocations example does: It processes the page content stream (which contains all those operations), updating a copy of the values of the current graphics state, and when it sees an operation for drawing a bitmap image, it uses the value of the current transformation matrix at that very moment:
protected void processOperator( PDFOperator operator, List arguments ) throws IOException
{
String operation = operator.getOperation();
if( INVOKE_OPERATOR.equals(operation) )
{
COSName objectName = (COSName)arguments.get( 0 );
Map<String, PDXObject> xobjects = getResources().getXObjects();
PDXObject xobject = (PDXObject)xobjects.get( objectName.getName() );
if( xobject instanceof PDXObjectImage )
{
PDXObjectImage image = (PDXObjectImage)xobject;
PDPage page = getCurrentPage();
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
double pageHeight = page.getMediaBox().getHeight();
System.out.println("*******************************************************************");
System.out.println("Found image [" + objectName.getName() + "]");
Matrix ctmNew = getGraphicsState().getCurrentTransformationMatrix();
...
[calculate dimensions and rotation of image on page]
...
Thus, for your task that PDFBox example should be a good starting point.

Related

WebView2 Maps API from point to lat long with higher dpi not accurate

I am using webview2 with google maps api scripts. With 96 dpi (100% scale) the static map is inserted correctly into AutoCAD, the lat and long are calculated correcly based on the screen point. If I increase the display scale higher than 100%, the lat and long are calculated wrong based on the screen point, the static map is inserted not far from the right position in AutoCAD.
What would be the solution with higher dpi?
Here is the script I use
function getMapType() {
return map.getMapTypeId();
}
function getMapZoom() {
return map.getZoom();
}
function getMapCenter() {
var c = map.getCenter();
return c.lat() + "|" + c.lng();
}
function getMapProjection() {
projection = map.getProjection();
topRight = projection.fromLatLngToPoint(map.getBounds().getNorthEast());
bottomLeft = projection.fromLatLngToPoint(map.getBounds().getSouthWest());
scale = 1 << map.getZoom();
}
function getLatLongByScreenPoint(x, y) {
var c = projection.fromPointToLatLng(new google.maps.Point(x / scale + bottomLeft.x, y / scale + topRight.y));
return c.lat() + "|" + c.lng();
}

Adjusting image contrast in FIB/SEM image - without affecting the text bar at the bottom of the image

FIB/SEM images have a text bar at the bottom of the image.
When imported into GMS, any contrast, gamma, .. adjustment also affects the text bar.
Is it possible to break up the image and have the data processing affect only the actual image - not the text bar?
The best you can do here is to break the actual image array into 2 separate images and then have the text-bar section displayed as a separate imageDisplay which you can add onto the imageDisplay of the data. You can shift/scale them with respect to each other, and you can also lock the added display so that it can not be shifted by mouse anymore. The following example should do what you need:
void CropAndMerge(Image img, number h){
number sx = img.ImageGetDimensionSize(0)
number sy = img.ImageGetDimensionSize(1)
image data := img.slice2(0,0,0,0,sx,1,1,sy-h,1).ImageClone() // note ":=", we sub-slice keeping tags and all
image anno = img.slice2(0,sy-h,0,0,sx,1,1,h,1) // note "=", we just want the data copy
imageDisplay disp
// Simple way to get imageDisplay. First show, then grab
// data.ShowImage()
// disp = data.ImageGetImageDisplay(0)
// Better alternative: No need to show
imageDocument doc = NewImageDocument( img.ImageGetName() )
doc.ImageDocumentAddImage( data )
// doc.ImageDocumentAddImage( anno ) // Use this to add 'side ordered' in case of page-view type. However, I'd rather not use page-mode.
disp = data.ImageGetImageDisplay(0)
disp.ImageDisplaySetColorTableByName( "Black Body Extended" ) // Just to show you can act on the display before actually showing it that way
// Add Annotation area as annotation on imageDisplay (all are components)
imageDisplay annoDisp = NewImageDisplay( anno, "best" )
disp.ComponentAddChildAtEnd( annoDisp )
// move out of the way
// ComponentPositionAroundPoint: Moves the annotation so the 'rel_x' horizontal point in the bounding rect is at 'new_x' (if bool horz is true), and for y accordinglye
number doVert = 1
number rel_y = 1.0 // bottom (relative coordinate!)
number new_y = sy // becomes bottom (absolute position)
annoDisp.ComponentPositionAroundPoint( 0,new_y,0,rel_y,0,doVert)
// make sure nobody messes with the annotation area
annoDisp.ComponentSetSelectable(0)
doc.ImageDocumentShow()
}
number sx = 1024
number sy = 1024
number h = 300
image in := realimage("Imported",4,sx,sy)
in = (icol%100 + iradius*sin(irow/iheight*5*Pi() + itheta )**2)
in.slice2(0,sy-h,0,0,sx,1,1,h,1) = (icol+irow)%50>45?max(in)+100:0
//in.showimage()
CropAndMerge(in.imageClone(),h)

How do I extract viewport from a pdf and modify an annotation's bounding rectangle according to the viewport?

I have implemented functionality to add link annotation to any pdf using pdfbox. It works well for most of pdfs, but for some pdfs it not placing markups at correct coordinates. And when I opened that pdf in some pdf editor, it gave me warning that the pdf contains an untitled viewport which might affect measurements for that pdf. So, I feel viewport is most probably causing the problem. Is there a way that I can modify the coordinates of markup according to viewport, so that it is placed at correct location in pdf. Here is a link to a pdf which contains the viewport.
According to Tilman's suggestion, I extracted the C entry from viewport's measure dictionary. And tried to modify rectangle's coordinate, but they are not getting added at the right location.Below is the code that I tried. Also, the viewport does not have effect on annotations, but it is causing problem when I try to draw something into the pdf.
COSArray vps = (COSArray)page.getCOSObject().getDictionaryObject(COSName.getPDFName("VP"));
if (vps != null)
{
for (int v = 0; v < vps.size(); ++v)
{
COSDictionary vp = (COSDictionary)vps.getObject(v);
PDViewportDictionary viewportDict = new PDViewportDictionary(vp);
PDRectangle vpRect = viewportDict.getBBox();
PDMeasureDictionary measureDict = viewportDict.getMeasure();
PDRectlinearMeasureDictionary rectilinearDict = new PDRectlinearMeasureDictionary(measureDict.getCOSObject());
bool pointLieInVP = UtilityClass.RectangleContainsPoint(new PointF(leftX, bottomY), vpRect);
if (pointLieInVP)
{
COSArray xArray = (COSArray)measureDict.getCOSObject().getDictionaryObject(COSName.getPDFName("X"));
float xScale = 1;
if (xArray!=null)
{
xScale = ((COSFloat)(((COSDictionary)xArray.getObject(0)).getDictionaryObject(COSName.getPDFName("C")))).floatValue();
}
leftX /= xScale;
rightX /= xScale;
COSBase yObj = measureDict.getCOSObject().getDictionaryObject(COSName.getPDFName("Y"));
if (yObj != null)
{
COSArray yArray = (COSArray)yObj;
float yScale = ((COSFloat)(((COSDictionary)yArray.getObject(0)).getDictionaryObject(COSName.getPDFName("C")))).floatValue();
bottomY /= yScale;
topY /= yScale;
}
else
{
bottomY /= xScale;
topY /= xScale;
}
}
}
}
Here is the link to pdf markups are added without adjusting for viewports. The 5 red colored markups are added at bottom right end of the page. But they should have been placed over the link annotations in the pdf which are placed at correct positions. And here is the link for pdf , in which markups are placed after modifying their coordinates using the above code. The markups do not appear at all.
This code (which does not avoid ClassCastExceptions) will show you the viewports in each page:
try (PDDocument doc = PDDocument.load(new File("S115-STRUCTURALHIGH ROOF FRAMING(WEST)ENLARGED PLANS.pdf")))
{
for (int p = 0; p < doc.getNumberOfPages(); ++p)
{
PDPage page = doc.getPage(p);
COSArray vps = (COSArray) page.getCOSObject().getDictionaryObject(COSName.getPDFName("VP"));
if (vps != null)
{
for (int v = 0; v < vps.size(); ++v)
{
COSDictionary vp = (COSDictionary) vps.getObject(v);
PDRectangle rect = new PDRectangle((COSArray) vp.getDictionaryObject(COSName.BBOX));
System.out.println("Viewport " + vp.getString(COSName.NAME) + ": " + rect);
}
}
}
}
How to adjust annotations is up to you... most likely, these should be inside the bbox. All you need to do is to adjust the rectangle of the annotations.

Kinect Fusion V2 export mesh with color as a STL or OBJ file

I am using the Kinect V2 and the Kinect Fusion Explorer V2 to record 3D scans. I am recording the color too, but if I am trying to export the mesh with color as a STL or OBJ file the mesh has no color. Only if I export a PLY file the mesh has a color. I need a STL file or a OBJ file and I think the MTL file is missing. I can't find anything in the Kinect documentation that only the PLY file contains color.
Try using 3D Scan from the Windows Store, extensions are .3mf and are Colour, or write your own:
Might be worth your time checking out the Kinect Fusion Example in the SDK: https://www.microsoft.com/en-au/download/details.aspx?id=44561
ColorMesh mesh = null;
Win32.SaveFileDialog dialog = new Win32.SaveFileDialog();
if (true == this.objFormat.IsChecked)
{
dialog.FileName = "MeshedReconstruction.obj";
dialog.Filter = "OBJ Mesh Files|*.obj|All Files|*.*";
}
using (StreamWriter writer = new StreamWriter(dialog.FileName))
{
// Default to flip Y,Z coordinates on save
KinectFusionHelper.SaveAsciiObjMesh(mesh, writer, true);
}
In the Helper Class:
/// <summary>
/// Save mesh in ASCII WaveFront .OBJ file
/// </summary>
/// <param name="mesh">Calculated mesh object</param>
/// <param name="writer">The text writer</param>
/// <param name="flipAxes">Flag to determine whether the Y and Z values are flipped on save,
/// default should be true.</param>
public static void SaveAsciiObjMesh(ColorMesh mesh, TextWriter writer, bool flipAxes)
{
if (null == mesh || null == writer)
{
return;
}
var vertices = mesh.GetVertices();
var normals = mesh.GetNormals();
var indices = mesh.GetTriangleIndexes();
// Check mesh arguments
if (0 == vertices.Count || 0 != vertices.Count % 3 || vertices.Count != indices.Count)
{
throw new ArgumentException(Properties.Resources.InvalidMeshArgument);
}
// Write the header lines
writer.WriteLine("#");
writer.WriteLine("# OBJ file created by Microsoft Kinect Fusion");
writer.WriteLine("#");
// Sequentially write the 3 vertices of the triangle, for each triangle
for (int i = 0; i < vertices.Count; i++)
{
var vertex = vertices[i];
string vertexString = "v " + vertex.X.ToString(CultureInfo.InvariantCulture) + " ";
if (flipAxes)
{
vertexString += (-vertex.Y).ToString(CultureInfo.InvariantCulture) + " " + (-vertex.Z).ToString(CultureInfo.InvariantCulture);
}
else
{
vertexString += vertex.Y.ToString(CultureInfo.InvariantCulture) + " " + vertex.Z.ToString(CultureInfo.InvariantCulture);
}
writer.WriteLine(vertexString);
}
// Sequentially write the 3 normals of the triangle, for each triangle
for (int i = 0; i < normals.Count; i++)
{
var normal = normals[i];
string normalString = "vn " + normal.X.ToString(CultureInfo.InvariantCulture) + " ";
if (flipAxes)
{
normalString += (-normal.Y).ToString(CultureInfo.InvariantCulture) + " " + (-normal.Z).ToString(CultureInfo.InvariantCulture);
}
else
{
normalString += normal.Y.ToString(CultureInfo.InvariantCulture) + " " + normal.Z.ToString(CultureInfo.InvariantCulture);
}
writer.WriteLine(normalString);
}
// Sequentially write the 3 vertex indices of the triangle face, for each triangle
// Note this is typically 1-indexed in an OBJ file when using absolute referencing!
for (int i = 0; i < vertices.Count / 3; i++)
{
string baseIndex0 = ((i * 3) + 1).ToString(CultureInfo.InvariantCulture);
string baseIndex1 = ((i * 3) + 2).ToString(CultureInfo.InvariantCulture);
string baseIndex2 = ((i * 3) + 3).ToString(CultureInfo.InvariantCulture);
string faceString = "f " + baseIndex0 + "//" + baseIndex0 + " " + baseIndex1 + "//" + baseIndex1 + " " + baseIndex2 + "//" + baseIndex2;
writer.WriteLine(faceString);
}
}
You can see, the .OBJ Vertex Colour's are not included by default - you need to include Vertex Colour information for ths .OBJ in the .MTL file.
mesh.VertexColors;
See: https://msdn.microsoft.com/en-us/library/microsoft.kinect.fusion.mesh.vertexcolors.aspx
batman.OBJ Header:
mtllib batman.mtl
which may contain some data like:
#
#
newmtl batman
Ns 96.078431
Ka 0.000000 0.000000 0.000000
Kd 0.640000 0.640000 0.640000
Ks 0.100000 0.100000 0.100000
Ni 1.000000
d 1.000000
...
Maybe check out: https://msdn.microsoft.com/en-us/library/dn435687.aspx for some more info on this.

How to resize bitmap image to be <200 KB and meet Tile restrictions (WinRT)

I am developing a routine to scale some bitmap images to be part of tile notifications for my Window-8 app
The tile images must be <200KB and less than 1024x1024 px in dimension. I am able to use a scaling routine to resize the source image as necessary to fit the 1024x1024 px dimension limitation.
How can I alter the source image to guarantee the size restriction will be met?
My first attempt was to continue to scale down the image until it clears the size threshold, and use isTooBig = destFileStream.Size > MaxBytes to determine the size. But, the code below results in an infinite loop. How can I reliably measure the size of the destination file?
bool isTooBig = true;
int count = 0;
while (isTooBig)
{
// create a stream from the file and decode the image
using (var sourceFileStream = await sourceFile.OpenAsync(Windows.Storage.FileAccessMode.Read))
using (var destFileStream = await destFile.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(sourceFileStream);
BitmapEncoder enc = await BitmapEncoder.CreateForTranscodingAsync(destFileStream, decoder);
double h = decoder.OrientedPixelHeight;
double w = decoder.OrientedPixelWidth;
if (h > baselinesize || w > baselinesize)
{
uint scaledHeight, scaledWidth;
if (h >= w)
{
scaledHeight = (uint)baselinesize;
scaledWidth = (uint)((double)baselinesize * (w / h));
}
else
{
scaledWidth = (uint)baselinesize;
scaledHeight = (uint)((double)baselinesize * (h / w));
}
//Scale the bitmap to fit
enc.BitmapTransform.ScaledHeight = scaledHeight;
enc.BitmapTransform.ScaledWidth = scaledWidth;
}
// write out to the stream
await enc.FlushAsync();
await destFileStream.FlushAsync();
isTooBig = destFileStream.Size > MaxBytes;
baselinesize *= .90d * ((double)MaxBytes / (double)destFileStream.Size);
}
}
Can you not calculate it using width x height x colourDepth (where colourDepth is in bytes, so 32bit=4bytes). Presumably you're maintaining aspect ratio so you just need to scale down width/height until you find it less than 200KB.
This assumes the output is an a bitmap and therefore uncompressed.
Considering that tile size either 150x150 for square tiles or 310x150 for wide tiles you should be able to shrink image down to the appropriate size and with jpeg compression you are pretty much guaranteed to be under 200k. Set compression quality around 80. It will give you good compression ratio while keeping decent image quality.