I have been writing a paint program in tkinter, and one of my functions saves the final canvas output (my image is rendererd on the canvas at coord 0,0). I save the canvas to memory as a postscript file and use PIL to save the postscript to disk as a PNG (PIL uses ghostscript when saving a postscript file). The canvas is always saved at 60.15% of it's original size, however. I would like to save the image as 100% of it's original size, though i can't figure out how to do this in my code.
Here is my code below:
"""my image is 256 x 256"""
ps = self.canvas_image.canvas.postscript(colormode='color', x = 0, y = 0,
height = 256, width = 256)
im = Image.open(BytesIO(ps.encode('utf-8')))
im.save(filepath, "PNG")
And here are my images (top is original image, bottom is saved image):
Turns out that postscript is a vectorized image format, and the image needs to be scaled before being rasterized. Your vector scale may be different than mine (mine was 0.60): you can view the scale factor in an encapsulated postscript file if the DPI scale factors in this code don't work for you
The open EPS code was taken from this post: how to maintain canvas size when converting python turtle canvas to bitmap
I used this code snippet to solve my problem:
ps = self.canvas_image.canvas.postscript(colormode='color', x = 0, y = 0, height = 256, width = 256)
""" canvas postscripts seem to be saved at 0.60 scale, so we need to increase the default dpi (72) by 60 percent """
im = open_eps(ps, dpi=119.5)
#im = Image.open('test.ps')
im.save(filepath, dpi=(119.5, 119.5))
def open_eps(ps, dpi=300.0):
img = Image.open(BytesIO(ps.encode('utf-8')))
original = [float(d) for d in img.size]
scale = dpi/72.0
if dpi is not 0:
img.load(scale = math.ceil(scale))
if scale != 1:
img.thumbnail([round(scale * d) for d in original], Image.ANTIALIAS)
return img
Related
My method doesn't resize the width and the height of the image it just resize the size of it.
I used a python library which is PIL.
import PIL
from PIL import Image
def resizeImg(img):
mywidth = 320
wpercent = (mywidth/float(img.size[0]))
hsize = int((float(img.size[1])*float(wpercent)))
img = img.resize((mywidth,hsize), PIL.Image.ANTIALIAS)
return img
The image was in 1MB now it's just 130KB, is that normal? I was expecting to get different width and height.
Yeah this looks normal.
What you're doing is that you are actually resampling the image.
If your input image as a horizontal pixel size superior to 320 pixels, you are effectively reducing the definition (number of pixels) hence the inferior file size.
If you don't want to change the definition but only want to change the printing/display size for example you might consider scaling the dpi.
This can be be done when saving by doing something like this :
img.save(filepath, format=fmt, dpi=(dpi, dpi))
You have to calculate the horizontal and vertical dpi value as desired.
This won't change the file size.
I want to crop an image and save it, but the problem is that the image changes hue significantly after saving the same format. Why is this happening? I'm not even converting it in the process.
Here is my code:
def square_crop(resized_image):
print "square crop"
width, height = resized_image.size
print width,height
edge_length = 3600
if(width >= height):
print("width>=height")
left = (width - edge_length)/2
top = (height - edge_length)/2
right = (width + edge_length)/2
bottom = (height + edge_length)/2
squared_image = resized_image.crop((left, top, right, bottom))
squared_image.save('squared.png')
And the confusing part is that this code uses the same image and saves it without hue change, so the cropping function must have an issue:
def image_resize(image):
print "image resize"
width, height = image.size
print width,height
if(width > 3601 and height > 3601):
print width, height
if(width >= height):
ratio = float(width)/float(height)
w = 3601 * ratio
h = 3601.0
print ratio, w, h
resized_image = image.resize([int(w), int(h)])
resized_image.save("resized.png")
else:
ratio = float(height)/float(width)
print(ratio)
w = 3601.0
h = 3601 * ratio
print ratio, w, h
resized_image = image.resize([int(w), int(h)])
resized_image.save("heic1509a_resized.png")
*EDIT: When I import .jpg file and save to .jpg both functions have the same hue issue. Same with .tif.
**EDIT: Also I've noticed that for some images this red color loss does not happen. I truly don't have any idea what is going on. I will leave the before and after screenshots to see for yourself.
Before - After
***EDIT: The problem is from the color space as the images that have changed color when saved were encoded using ProPhoto RGB color space(ROMM RGB (Reference Output Medium Metric)).
I am using gimp2 to convert them first to RGB without losing color, but I would want to find a way to do that automatically from python.
I will post any new updates on this issue.
The problem was that when I was saving the file, the PIL library automatically switched the color space of the image(ROMM-RGB) to other color space(RGB or sRGB) and basically every color changed.
All you have to do is preserve the color space of the image and you're fine. If you want to convert to another color space you should look up OpenCV library.
I can't explain too much in detail because I am just breaking the ice on this. Here is the code that solved this issue:
resized_image.save('resized.jpg', #file name
format = 'JPEG', #format of the file
quality = 100, #compression quality
icc_profile = resized_image.info.get('icc_profile','')) #preserve the icc profile of the photo(this was the one that caused problems)
Here is a link to a more in-depth answer: LINK
I have many images of different sizes in my directory, however i want to resize them all by a certain ratio, let's say 0.25 or 0.2, it should be a variable i can control from my code and i want the resulting images to be an output in another directory.
I looked into this approach supplied by this previous question How to resize an image in python, while retaining aspect ratio, given a target size?
Here is my approach,
aspectRatio = currentWidth / currentHeight
heigth * width = area
So,
height * (height * aspectRatio) = area
height² = area / aspectRatio
height = sqrt(area / aspectRatio)
At that point we know the target height, and width = height * aspectRatio.
Ex:
area = 100 000
height = sqrt(100 000 / (700/979)) = 373.974
width = 373.974 * (700/979) = 267.397
but it lacks lots of details for example:how to transform these sizes back on the image which libraries to use and so on..
Edit: looking more into the docs img.resize looks ideal (although i also noticed .thumbnail) but i can't find a proper example on a case like mine.
from PIL import Image
ratio = 0.2
img = Image.open('/home/user/Desktop/test_pic/1-0.png')
hsize = int((float(img.size[1])*float(ratio)))
wsize = int((float(img.size[0])*float(ratio)))
img = img.resize((wsize,hsize), Image.ANTIALIAS)
img.save('/home/user/Desktop/test_pic/change.png')
You can create your own small routine to resize and resave pictures:
import cv2
def resize(oldPath,newPath,factor):
"""Resize image on 'oldPath' in both dimensions by the same 'factor'.
Store as 'newPath'."""
def r(image,f):
"""Resize 'image' by 'f' in both dimensions."""
newDim = (int(f*image.shape[0]),int(f*image.shape[1]))
return cv2.resize(image, newDim, interpolation = cv2.INTER_AREA)
cv2.imwrite(newPath, r(cv2.imread(oldPath), factor))
And test it like so:
# load and resize (local) pic, save as new file (adapt paths to your system)
resize(r'C:\Pictures\2015-08-05 10.58.36.jpg',r'C:\Pictures\mod.jpg',0.4)
# show openened modified image
cv2.imshow("...",cv2.imread(r'C:\Users\partner\Pictures\mod.jpg'))
# wait for keypress for diplay to close
cv2.waitKey(0)
You should add some error handling, f.e.:
no image at given path
image not readable (file path permissions)
image not writeable (file path permissions)
I have a generated Image (with PIL) and I have to create a PDF with specific size that it will contains this (full size) image.
I start from size= 150mm x 105mm
I generated the corresponding image 1818px x 1287px (with small border)
(mm to px with 300dpi)
I use this code
pp = 25.4 # 1 pp = 25,4mm
return int((dpi * mm_value) / pp)
Now I have to create the PDF file with size page = 150mm x 105mm
I use reportlab and I would a pdf with the best image quality (to print).
Is possible to specify this?
Is correct to create the PDF page size with this:
W = ? // inch value??
H = ? // inch value??
buffer = BytesIO()
p = canvas.Canvas(buffer)
p.setPageSize(size=(W, H))
and to draw the image:
p.drawImage(img, 0, 0, width=img.width, preserveAspectRatio=True, mask='auto', anchor='c')
The trick is to scale reportlab's Canvas before drawing the image onto it. It doesn't seem to pick up correctly the DPI information from the file.
This example code works pretty well for my laserprinter:
from PIL import Image, ImageDraw, ImageFont
import reportlab.pdfgen.canvas
from reportlab.lib.units import mm
# Create an image with 300DPI, 150mm by 105mm.
dpi = 300
mmwidth = 150
mmheight = 105
pixwidth = int(mmwidth / 25.4 * dpi)
pixheight = int(mmheight / 25.4 * dpi)
im = Image.new("RGB", (pixwidth, pixheight), "white")
dr = ImageDraw.Draw(im)
dr.rectangle((0, 0, pixwidth-1, pixheight-1), outline="black")
dr.line((0, 0, pixwidth, pixheight), "black")
dr.line((0, pixheight, pixwidth, 0), "black")
dr.text((100, 100), "I should be 150mm x 105mm when printed, \
with a thin black outline, at 300DPI", fill="black")
# A test patch of 300 by 300 individual pixels,
# should be 1 inch by 1 inch when printed,
# to verify that the resolution is indeed 300DPI.
for y in range(400, 400+300):
for x in range(500, 500+300):
if x & 1 and y & 1:
dr.point((x, y), "black")
im.save("safaripdf.png", dpi=(dpi, dpi))
# Create a PDF with a page that just fits the image we've created.
pagesize = (150*mm, 105*mm)
c = reportlab.pdfgen.canvas.Canvas("safaripdf.pdf", pagesize=pagesize)
c.scale(0.24, 0.24) # Scale so that the image exactly fits the canvas.
c.drawImage("safaripdf.png", 0, 0) # , width=pixwidth, height=pixheight)
c.showPage()
c.save()
You might want to tweak the scale values a little bit so that the dimensions fit exactly your printer, but the values above come pretty close. I've checked it with a ruler ;-)
I am probably looking for the wrong thing in the handbook, but I am looking to take an image object and expand it without resizing (stretching/squishing) the original image.
Toy example: imagine a blue rectangle, 200 x 100, then I perform some operation and I have a new image object, 400 x 300, consisting of a white background upon which a 200 x 100 blue rectangle rests. Bonus if I can control in which direction this expands, or the new background color, etc.
Essentially, I have an image to which I will be adding iteratively, and I do not know what size it will be at the outset.
I suppose it would be possible for me to grab the original object, make a new, slightly larger object, paste the original on there, draw a little more, then repeat. It seems like it might be computationally expensive. However, I thought there would be a function for this, as I assume it is a common operation. Perhaps I assumed wrong.
The ImageOps.expand function will expand the image, but it adds the same amount of pixels in each direction.
The best way is simply to make a new image and paste:
newImage = Image.new(mode, (newWidth,newHeight))
newImage.paste(srcImage, (x1,y1,x1+oldWidth,y1+oldHeight))
If performance is an issue, make your original image bigger than needed and crop it after the drawing is done.
Based on interjays answer:
#!/usr/bin/env python
from PIL import Image
import math
def resize_canvas(old_image_path="314.jpg", new_image_path="save.jpg",
canvas_width=500, canvas_height=500):
"""
Resize the canvas of old_image_path.
Store the new image in new_image_path. Center the image on the new canvas.
Parameters
----------
old_image_path : str
new_image_path : str
canvas_width : int
canvas_height : int
"""
im = Image.open(old_image_path)
old_width, old_height = im.size
# Center the image
x1 = int(math.floor((canvas_width - old_width) / 2))
y1 = int(math.floor((canvas_height - old_height) / 2))
mode = im.mode
if len(mode) == 1: # L, 1
new_background = (255)
if len(mode) == 3: # RGB
new_background = (255, 255, 255)
if len(mode) == 4: # RGBA, CMYK
new_background = (255, 255, 255, 255)
newImage = Image.new(mode, (canvas_width, canvas_height), new_background)
newImage.paste(im, (x1, y1, x1 + old_width, y1 + old_height))
newImage.save(new_image_path)
resize_canvas()
You might consider a rather different approach to your image... build it out of tiles of a fixed size. That way, as you need to expand, you just add new image tiles. When you have completed all of your computation, you can determine the final size of the image, create a blank image of that size, and paste the tiles into it. That should reduce the amount of copying you're looking at for completing the task.
(You'd likely want to encapsulate such a tiled image into an object that hid the tiling aspects from the other layers of code, of course.)
This code will enlarge a smaller image, preserving aspect ratio, then center it on a standard sized canvas. Also preserves transparency, or defaults to gray background.
Tested with P mode PNG files.
Coded debug final.show() and break for testing. Remove lines and hashtag on final.save(...) to loop and save.
Could parameterize canvas ratio and improve flexibility, but it served my purpose.
"""
Resize ... and reconfigures. images in a specified directory
Use case: Images of varying size, need to be enlarged to exaxtly 1200 x 1200
"""
import os
import glob
from PIL import Image
# Source directory plus Glob file reference (Windows)
source_path = os.path.join('C:', os.sep, 'path', 'to', 'source', '*.png')
# List of UNC Image File paths
images = glob.glob(source_path)
# Destination directory of modified image (Windows)
destination_path = os.path.join('C:', os.sep, 'path', 'to', 'destination')
for image in images:
original = Image.open(image)
# Retain original attributes (ancillary chunks)
info = original.info
# Retain original mode
mode = original.mode
# Retain original palette
if original.palette is not None:
palette = original.palette.getdata()[1]
else:
palette = False
# Match original aspect ratio
dimensions = original.getbbox()
# Identify destination image background color
if 'transparency' in info.keys():
background = original.info['transparency']
else:
# Image does not have transparency set
print(image)
background = (64)
# Get base filename and extension for destination
filename, extension = os.path.basename(image).split('.')
# Calculate matched aspect ratio
if dimensions[2] > dimensions[3]:
width = int(1200)
modifier = width / dimensions[2]
length = int(dimensions[3] * modifier)
elif dimensions[3] > dimensions[2]:
length = int(1200)
modifier = length / dimensions[3]
width = int(dimensions[2] * modifier)
else:
width, length = (1200, 1200)
size = (width, length)
# Set desired final image size
canvas = (1200, 1200)
# Calculate center position
position = (
int((1200 - width)/2),
int((1200 - length)/2),
int((1200 - width)/2) + width,
int((1200 - length)/2) + length
)
# Enlarge original image proportionally
resized = original.resize(size, Image.LANCZOS)
# Then create sized canvas
final = Image.new(mode, canvas, background)
# Replicate original properties
final.info = info
# Replicate original palatte
if palette:
final.putpalette(palette)
# Cemter paste resized image to final canvas
final.paste(resized, position)
# Save final image to destination directory
final.show()
#final.save("{}\\{}.{}".format(destination_path, filename, extension))
break