Store DPI info in EPS image - python

I’m trying to store DPI information in an Encapsulated PostScript (EPS) image with Pillow library.
For instance:
from PIL import Image
size = 650, 827
path = 'grey.eps'
image = Image.new('L', size, color='grey')
image.save(path, dpi=(300, 300))
But I can’t retrieve this info on reading:
image = Image.open(path)
dpi = image.info['dpi']
print(dpi)
I get:
Traceback (most recent call last):
...
KeyError: 'dpi'
Is it possible to have DPI information in EPS images or is it a limitation to the Pillow library?

You certainly can't store 'dpi information' in an EPS. The DPI of the image depends on how its printed.
If you take an image 100x100 samples and print it on a 1 inch square, then the dpi of the image is 100 dpi. If I take the same image and print it on a 2 inch square, then the dpi of the image is 50 dpi.
One of the points of an EPS file is that you can rescale the EPS to fit the content of the page, so the dpi of the image depends entirely on what scaling is applied to it.
EPS is not a simple bitmap format, like say TIFF, PostScript is a complete programming language and includes primitives for all sorts of vector drawing operations, as well as bitmaps.

Related

How to rasterize SVG in Python at arbitrary size

How do you convert an SVG image to PNG, but proportionally scale it up, using Python?
I have an SVG "sprite" that I'm trying to load at different resolutions, small/medium/large/etc.
I tried some of the answers suggested in this question like:
svg = Parser.parse_file(filename)
rast = Rasterizer()
buff = rast.rasterize(svg, w, h)
image = pygame.image.frombuffer(buff, (w, h), 'ARGB')
However, none of them work as expected. Specifically, the width and height parameters have no effect on the size of the rasterized pixels, only the overall size of the PNG. Whether I use w=10, h=10 or w=10000, h=10000, the image contains the same rasterized image (whose dimensions I suspect are being pulled from the root width/height/viewbox parameters in my svg file), but the larger dimensions just have a ton more padding.
I don't have the larger image to just be the smaller image with a lot of extra empty space. I want the larger image to be a scaled up version of the smaller image. How do I do this?
Here my take on this problem:
from PIL import Image # to convert into any image format
from cairosvg import svg2png
img_width, img_height = 1024,768
with open("example.svg","rb") as f:
svg_data = f.read() ## binary SVG data from any source
svg_png_image = svg2png(bytestring=svg_data, output_width=img_width, output_height=img_height) # convert to PNG with img_width/img_height
img1 = Image.open(BytesIO(svg_png_image)) # pass to PIL
img1.save(buf, format='JPEG', compress_level=1) # or PNG or any other
image_data_buf = buf.getvalue()
In my case, I set img_width, img_height according to my image that I will paste it into.
You may find many interesting examples of use here.
Also, there is the following way to use svg2png:
cairo.svg2png(url="/path/to/input.svg", write_to="/tmp/output.png")
I didn't find a detailed description of function svg2png, but you derive the purposes from parameters naming. Hope someone will add it to this article.

How to adjust Pillow EPS to JPG quality

I'm trying to convert EPS images to JPEG using Pillow. But the results are of low quality. I'm trying to use resize method, but it gets completely ignored. I set up the size of JPEG image as (3600, 4700), but the resulted image has (360, 470) size. My code is:
eps_image = Image.open('img.eps')
height = eps_image.height * 10
width = eps_image.width * 10
new_size = (height, width)
print(new_size) # prints (3600, 4700)
eps_image.resize(new_size, Image.ANTIALIAS)
eps_image.save(
'img.jpeg',
format='JPEG'
dpi=(9000, 9000),
quality=95)
UPD. Vasu Deo.S noticed one my error, and thanks to him the JPG image has become bigger, but quality is still low. I've tried different DPI, sizes, resample values for resize function, but the result does not change much. How can i make it better?
The problem is that PIL is a raster image processor, as opposed to a vector image processor. It "rasterises" vector images (such as your EPS file and SVG files) onto a grid when it opens them because it can only deal with rasters.
If that grid doesn't have enough resolution, you can never regain it. Normally, it rasterises at 100dpi, so if you want to make bigger images, you need to rasterise onto a larger grid before you even get started.
Compare:
from PIL import Image
eps_image = Image.open('image.eps')
eps_image.save('a.jpg')
The result is 540x720:
And this:
from PIL import Image
eps_image = Image.open('image.eps')
# Rasterise onto 4x higher resolution grid
eps_image.load(scale=4)
eps_image.save('a.jpg')
The result is 2160x2880:
You now have enough quality to resize however you like.
Note that you don't need to write any Python to do this at all - ImageMagick will do it all for you. It is included in most Linux distros and is available for macOS and Windows and you just use it in Terminal. The equivalent command is like this:
magick -density 400 input.eps -resize 800x600 -quality 95 output.jpg
It's because eps_image.resize(new_size, Image.ANTIALIAS) returns an resized copy of an image. Therefore you have to store it in a separate variable. Just change:-
eps_image.resize(new_size, Image.ANTIALIAS)
to
eps_image = eps_image.resize(new_size, Image.ANTIALIAS)
UPDATE:-
These may not solve the problem completely, but still would help.
You are trying to save your output image as a .jpeg, which is a
lossy compression format, therefore information is lost during the
compression/transformation (for the most part). Change the output
file extension to a lossless compression format like .png so that
data would not be compromised during compression. Also change
quality=95 to quality=100 in Image.save()
You are using Image.ANTIALIAS for resampling the image, which is
not that good when upscaling the image (it has been replaced by
Image.LANCZOS in newer version, the clause still exists for
backward compatibility). Try using Image.BICUBIC, which produces
quite favorable results (for the most part) when upscaling the image.

On dpi option from imsave

Consider the following MWE to generate a random image:
import matplotlib.pyplot as plt
import numpy as np
pts = np.random.random_sample((1024, 1024))
plt.imsave('foo.png',pts, dpi=300)
I'm trying to understand how the dpi option works. According to the matplotlib.pyplot.imsave documentation,
dpi : int
The DPI to store in the metadata of the file. This does not affect the resolution of the output image.
The output of the program above is a 1024x1024 image file.
What I don't understand is the fact that neither identify -verbose foo.png nor exiftool foo.png shows the image resolution.
But, opening it with ImageMagick (display) and checking the image info, I find
So, what is the math behind the resolution and printing size values?
How to obtain a 300dpi resolution image?
Image formats like png do not have a dpi defined. If you save a 1024 x 1024 pixel array via imsave, the image will simply be 1024 x 1024 pixel.
Imagemagick seems to ignore any metadata, so it assumes a resolution of 96 dpi. From the pixel size (1024) and the dpi (96) it then calculates the size in inches to be
1024 dots / 96 dots per inch = 10.667 inch
That said, the question "How to obtain a 300dpi resolution image?" is not really clear. But most graphics viewers would allow to scale the image prior to printing, so it shouldn't be a problem to get a 300 dpi print on paper.
In ImageMagick, you can set the output density by
convert image <processing> -density 300 newimage
Then to check the density you can do either
identify -verbose newimage
or
identify -format "%xx%y"
to find the density (resolution)

how to increase dpi with opencv?

I need to increase the dpi of my image before reading with ocr in opencv. The problems are :
I do not know the dpi of my image right now
I do not know how to increase the dpi of an image
I searched in Google, and almost every answer suggests using cv2.resize
image = cv2.imread("source.png")
resized_image = cv2.resize(image, (100, 50)) #I need to change it to 300 DPI
resize only changes the size of image, but after all does not increase the dpi. I tried to use it, and then checked in Photoshop, the dpi was not changed.
How to do it with opencv?
I need to change dpi to 300, why do I need to know current dpi? Because if it is already dpi > 300, I do not need to convert it.
I do it with python.
The dpi is just a number in the JPEG/TIFF/PNG header. It is entirely irrelevant to the world and his dog until you print the image and then it determines how large the print will be given the image's dimensions in pixels.
During image processing, it is irrelevant. The only thing of any interest is the number of pixels you have. That is the ultimate determinant of image quality, or information content - however you want to describe it.
I don't believe you can set it with OpenCV. You can certainly set it with ImageMagick like this in the Terminal:
mogrify -set density 300 *.png # v6 ImageMagick
magick mogrify -set density 300 *.png # v7 ImageMagick
You can check it with:
identify -format "Density: %x x %y" SomeImage.jpg # v6 ImageMagick
magick identify -format ... as above # v7 ImageMagick
You can do similar things with exiftool in Terminal - note that exiftool is MUCH smaller and easier to maintain than ImageMagick because it is "just" a (very capable) single Perl script:
Extract image resolution from EXIF IFD1 information:
exiftool -IFD1:XResolution -IFD1:YResolution image.jpg
Extract all tags with names containing the word "Resolution" from an image|:
exiftool '-*resolution*' image.jpg
Set X/Y Resolution (density) on image.jpg:
exiftool -xresolution=300 -yresolution=300 image.jpg
Here is a little demonstration of what I mean at the beginning of my answer...
Use ImageMagick to create an image 1024x768 with no dpi information:
convert -size 1024x768 xc:black image.jpg
Now examine it:
identify -verbose image.jpg
Image: image.jpg
Format: JPEG (Joint Photographic Experts Group JFIF format)
Mime type: image/jpeg
Class: PseudoClass
Geometry: 1024x768+0+0
Units: Undefined
Colorspace: Gray
Type: Bilevel
...
...
Now change the dpi and set the dpi units and examine it again:
mogrify -set density 300 -units pixelsperinch image.jpg # Change dpi
identify -verbose image.jpg # Examine
Image: image.jpg
Format: JPEG (Joint Photographic Experts Group JFIF format)
Mime type: image/jpeg
Class: PseudoClass
Geometry: 1024x768+0+0 <--- Number of pixels is unchanged
Resolution: 300x300 <---
Print size: 3.41333x2.56 <--- Print size is now known
Units: PixelsPerInch <---
Colorspace: Gray
Type: Bilevel
...
...
And now you can see that suddenly we know how big a print will come out and that the number of pixels has not changed.
Even though this is an old post I just wanted to say that Tesseract has been tested and found to operate better when the height of the characters is around 30 pixels. Please check the following link:
https://groups.google.com/forum/#!msg/tesseract-ocr/Wdh_JJwnw94/24JHDYQbBQAJ
DPI is inherited property of graphical device - monitor, scanner, camera, etc. For example - lets say that we are scanning image, and we want to get image with better quality - so we set higher DPI value in scanner options. If no better DPI option - so we need to buy a better scanner which supports more scan resolutions. There are devices/methods which are able to achieve 100 000 DPI

How to save an .EPS file to PNG with transparency in Python

I'm building a Paint-like app Since I want the freedom to reposition and modify the shape properties later, I am using Tkinter to draw shapes on Canvas instead of PIL Draw or anything else. From other answers, I found how to save a canvas as PNG by 1st creating a postscript file and then converting it to PNG using PIL.
Now the problem is the EPS file has transparent spaces but the PNG file fills those voids with a White background color. I'm not sure where I am going wrong.
Below is the function I used.
def saveImg(event):
global canvas
canvas.postscript(file="my_drawing.eps", colormode='color')
imgNew = Image.open("my_drawing.eps")
imgNew.convert("RGBA")
imgNew.thumbnail((2000,2000), Image.ANTIALIAS)
imgNew.save('testImg.png', quality=90)
Looks like transparency is not supported. From the docs:
The EPS driver can read EPS images in L, LAB, RGB and CMYK mode, but Ghostscript may convert the images to RGB mode rather than leaving them in the original color space.
When you load in RGB (instead of RGBA) the alpha channel information is discarded and converting it to RGBA later will not recover it.
Your best shot is porting it to more recent toolkits like cairo or QT or converting the file using GhostScript directly as suggested by PM2Ring.
For the GS approach in order to set the width and height of the output file you must use the -rN switch where N is the resolution in PPI (pixels per inch). You must do the math in order to get target resolution from the EPS bounding box and the desired output size.
Or you can render to a fixed resolution first, lets say, 100 PPI, see the width you got and do the math in order to get the correct resolution. For example, if rendering with -r100 gives you a file 500 pixels wide but you want it to be 1024:
desired_resolution = initial_resolution * desired_width // initial_width
In order to get a file 1024 pixels wide:
>>> 100 * 1024 // 500
204
So you must render the EPS again using -r204.
Edit 1:
I got the solution from this Question
We can set custom width and height using -gNNNNxMMMM
but the dpi value crops only a small area. I tried with the usual 72dpi and I got a decent output(I'm not sure if it's perfect or not). Now I need to find how to execute this command every time when I run the program and provide the custom image size value. :\

Categories

Resources