I'm trying to write a code to merge 2 photos side by side onto a new image, and I found this script online--however, I have no idea how it works. Where do I input the image files that I want to merge? Can someone please explain this code to me? Thanks!!
from PIL import Image
import sys
if not len(sys.argv) > 3:
raise SystemExit("Usage: %s src1 [src2] .. dest" % sys.argv[0])
images = map(Image.open, sys.argv[1:-1])
w = sum(i.size[0] for i in images)
mh = max(i.size[1] for i in images)
result = Image.new("RGBA", (w, mh))
x = 0
for i in images:
result.paste(i, (x, 0))
x += i.size[0]
result.save(sys.argv[-1])
It is very easy. You open the images with names provided in sys.argv (arguments to the program):
images = map(Image.open, sys.argv[1:-1])
You calculate the new width (sum of all width for opened images, which is i.size[0])
w = sum(i.size[0] for i in images)
The height, which should be equal to the height of the highest image (this way it can fit each)
mh = max(i.size[1] for i in images)
Create an image with calculated dumentions
result = Image.new("RGBA", (w, mh))
For each image opened, insert it (with paste function) to the point of x pixels from the left and 0 from the top and add the width of inserted image to x so that the next one is adjacent, not overlapping
x = 0
for i in images:
result.paste(i, (x, 0))
x += i.size[0]
Save the image
result.save(sys.argv[-1])
The error processing you see at the top has nothing to do with the process of merging images, but rather asserts that there is a correct number of arguments to the program
Related
I am writing a program where I chop up an image into many sub-tiles, process the tiles, then stitch them together. I am stuck at the stitching part. When I run my code, after the first row the tiles each shift one space over. I am working with 1000x1000 tiles and the image size can be variable. I also get this ugly horizontal padding that I can't figure out how to get rid of.
Here is a google drive link to the images:
https://drive.google.com/drive/folders/1HqRl29YlWUrsYoZP88TAztJe9uwgP5PS?usp=sharing
Clarification based on the comments
I take the original black and white image and crop it into 1000px x 1000px black and white tiles. These tiles are then re-colored to replace the white with a color corresponding to a density heatmap. The recolored tiles are then saved into that folder. The picture I included is one of the colored in tiles that I am trying to piece back together. When pieced together it should be the same shape but multi colored version of the black and white image
from PIL import Image
import os
stitched_image = Image.new('RGB', (large_image.width, large_image.height))
image_list = os.listdir('recolored_tiles')
current_tile = 0
for i in range(0, large_image.height, 1000):
for j in range(0, large_image.width, 1000):
p = Image.open(f'recolored_tiles/{image_list[current_tile]}')
stitched_image.paste(p, (j, i), 0)
current_tile += 1
stitched_image.save('test.png')
I am attaching the original image that I process in tiles and the current state of the output image:
An example of the tiles found in the folder recolored_tiles:
First off, the code below will create the correct image:
from PIL import Image
import os
stitched_image = Image.new('RGB', (original_image_width, original_image_height))
image_list = os.listdir('recolored_tiles')
current_tile = 0
for y in range(0, original_image_height - 1, 894):
for x in range(0, original_image_width - 1, 1008):
tile_image = Image.open(f'recolored_tiles/{image_list[current_tile]}')
print("x: {0} y: {1}".format(x, y))
stitched_image.paste(tile_image, (x, y), 0)
current_tile += 1
stitched_image.save('test.png')
Explanation
First off, you should notice, that your tiles aren't 1000x1000. They are all 1008x984 because 18145x16074 can't be divided up into 19 1000x1000 tiles each.
Therefore you will have to put the correct tile width and height in your for loops:
for y in range(0, 16074, INSERT CURRECT RECOLORED_TILE HEIGHT HERE):
for x in range(0, 18145, INSERT CURRECT RECOLORED_TILE WIDTH HERE):
Secondly, how python range works, it doesn't run on the last digit. Representation:
for i in range(0,5):
print(i)
The output for that would be:
0
1
2
3
4
Therefore the width and height of the original image will have to be minused by 1, because it thinks you have 19 tiles, but there isn't.
Hope this works and what a cool project you're working on :)
So basically I've got 2 codes:
One creates a number of different coloured images with 1 pixel dimensions
The other combines all the created images into one
The first one works perfectly but in the second code I get an error: IOError: [Errno 24] Too many open files: 'Test 3161.png'
The thing is I don't necessarily want to create the files. What I really want is the combined image at the end. I'm not sure how to approach this. Any help would be greatly appreciated.
Code 1 - Creating images
from PIL import Image
import sys
im = Image.new("RGB", (1, 1))
pix = im.load()
j=0
for r in range(65,130):
for g in range(65,130):
for b in range(65,130):
for x in range(1):
for y in range(1):
axis = (r,g,b)
pix[x,y] = axis
print axis
j+=1
im.save('Test {}.png'.format(j), "PNG")
Code 2: Combining images
from PIL import Image
import sys
import glob
imgfiles = []
for file in glob.glob("*.png"):
imgfiles.append(file)
print imgfiles
#stitching images together
images = map(Image.open, imgfiles)
widths, heights = zip(*(i.size for i in images))
total_width = sum(widths)
max_height = max(heights)
new_im = Image.new('RGB', (total_width, max_height))
x_offset = 0
for im in images:
new_im.paste(im, (x_offset,0))
x_offset += im.size[0]
new_im.save('test.png')
This is somewhat the final image I'm trying to get but not with as many colours as shown in it:
The coloured images that are created from code 1 are images that are 1 pixel in width and diameter. For example like this:
Its harder to see as its one pixel right next to this. It looks like a fullstop but is the 1 pixel image in question.
I still don't understand what you expect to produce, but this should be close and a lot faster and easier:
#!/usr/local/bin/python3
from PIL import Image
import numpy as np
# Create array to hold output image
result=np.zeros([1,13*13*13,3],dtype=np.uint8)
j=0
for r in range(65,130,5):
for g in range(65,130,5):
for b in range(65,130,5):
result[0,j]= (r,g,b)
j+=1
# Convert output array to image and save
im=Image.fromarray(result)
im.save("result.jpg")
Note that the above script is intended to do the job of both of your scripts in one go.
Note that I made the result image a bit taller (fatter) so you can see it, in fact it is only 1 pixel high.
Note that I added a step of 5 to make the output image smaller because it otherwise exceeds the size limits - for JPEG at least.
Note that I coarsely guessed the array width (13*13*13) on the basis of (130-65)/5, because I don't really understand your requirements.
To solve the too-many-open-files error, you can make a little function:
def getImageDetails(imgfile):
im = Image.open(imgfile)
size = im.size
im.load() # closes the pointer after it loads the image
return size[0], size[1], im
widths, heights, images = zip(*(getImageDetails(i) for i in imgfiles))
replace these lines with the code above:
images = map(Image.open, imgfiles)
widths, heights = zip(*(i.size for i in images))
http://i.stack.imgur.com/AAtUD.jpg
http://i.stack.imgur.com/eouLY.jpg
images to use for code.
the end result i am trying to do is combine the vignette picture and the CGI picture because the vignette images RGB values are darker towards the edges i need to multiply the original images corresponding pixels by the smaller numbers towards the edges, should make the picture have a darker frame around the edges of the original picture.
here's the code so far:
def addVignette(inputPic, vignette):
#create empty canvas to combine images correctly
canvas = makeEmptyPicture(getWidth(inputPic), getHeight(inputPic))
for x in range(0, getWidth(inputPic)):
for y in range(0, getHeight(inputPic)):
px = getPixel(canvas, x, y)
inputPx = getPixel(inputPic, x, y)
vignettePx = getPixel(vignette, x, y)
#make a new color from these values
newColour = getNewColorValues(vignettePx,inputPx)
#then assign this new color to the current pixel of the input image
setColor(px, newColour)
explore(canvas)
def getNewColourValues(inputPx, vignettePx):
inputRed = getRed(inputPx)
vignetteRed = getRed(vignettePx)
inputGreen = getGreen(inputPx)
vignetteGreen = getGreen(vignettePx)
inputBlue = getBlue(inputPx)
vignetteBlue = getBlue(vignettePx)
newRGB= setColor(inputPx,inputRed,inputGreen,inputBlue)*(vignettePx,vignetteRed,vignetteGreen,vignetteBlue)
newColour = makeColor(newRGB)
return newColour
def newPicture(newColour):
folder = pickAFolder()
filename = requestString("enter file name: ")
path = folder+filename+".jpg"
writePictureTo(inputPic, path)
when testing use vignette_profile image first then CGI image also the saving image doesnt work even though i've been trying to get it to work any help will be appreciated.
Saving the image
Let me start with saving the image. What I can see from the code you posted, you never actually call the newPicture() function which is why it's not saving the image. Also I noticed in the newPicture function that you don't pass a reference of the new image to the function.
Please my solution to this below. I have changed the function name from newPicture to saveNewImage()
Adding the Vignette
Please see the comments for the code block denote by ******* in the getNewColorValues() function.
You run the Main() function for this script to work
# Main function.
# *** THIS FUNCTION NEEDS TO BE CALLED IN THE CONSOLE ***
# i.e >>> main()
def main():
# Choose the files you wish to use
inputFile = pickAFile()
vignetteFile = pickAFile()
# Turn both files into picture objects
inputPic = makePicture(inputFile)
vignette = makePicture(vignetteFile)
# addVignette() function combines the input picture and vignette together
# and returns the result as a new picture object
newImage = addVignette(inputPic, vignette)
# saveNewImage() function stores the new image as file
saveNewImage(newImage)
# Main() calls this function to add input picture and vignette together
def addVignette(inputPic, vignette):
# Create empty canvas
canvas = makeEmptyPicture(getWidth(inputPic), getHeight(inputPic))
# Iterate through all the pixels of the input image. x and y are
# used as the current coordinates of the pixel
for x in range(0, getWidth(inputPic)):
for y in range(0, getHeight(inputPic)):
# Get the current pixels of inputPic and vignette
inputPixel = getPixel(inputPic, x, y)
vignettePixel = getPixel(vignette, x, y)
# The getNewColorValues() function, makes a new color from those
# values
newColor = getNewColorValues(inputPixel, vignettePixel)
# Assign this new color to the current pixel of the canvas
px = getPixel(canvas, x, y)
setColor(px, newColor)
# Show the result of combiming the input picture with the vignette
explore(canvas)
# return the new image to main() function.
return canvas
# Called from the addVignette() function to add the color values from
# the input picture and vignette together. It returns a new color
# object
def getNewColorValues(inputPixel, vignettePixel):
# Get the individual colour values
inputRed = getRed(inputPixel)
vignetteRed = getRed(vignettePixel)
inputGreen = getGreen(inputPixel)
vignetteGreen = getGreen(vignettePixel)
inputBlue = getBlue(inputPixel)
vignetteBlue = getBlue(vignettePixel)
# ***********************************************************
# Most important part. This will determine if the pixel is darkent
# and by how much. How it works is the darker the vignette pixel the less that will
# be taken away from 255. This means the result of `255 - vignetteRed` will be a higher
# value which means more will be taken away from the input colour.
# The light the vignette pixel the less that will be taken away from input pixel
newR = inputRed - (255 - vignetteRed)
newG = inputGreen - (255 - vignetteGreen)
newB = inputBlue - (255 - vignetteBlue)
# ***********************************************************
newC = makeColor(newR, newG, newB)
return newC
# Called from the main() function in order to save the new image
def saveNewImage(newImage):
folder = pickAFolder()
filename = requestString("Please enter file name: ")
path = folder + filename + ".jpg"
writePictureTo(newImage, path)
You can also try doing this in CV. Single pixel manipulation and file I/O are pretty straight forward.
img = cv2.imread('test.jpg')
pixel = img[10,10]
Ive never had any issues with file I/O in CV. Chances are its a permission error or excess white space.
cv2.imwrite('messigray.png',img)
You can also do some easy image previewing which in this case would let you experiment with the output a little more.
I am trying to find the exact pixel height and length of a piece of red text on an blank black image. To do this I am taking the pixel values of the red colours and copying and pasting it into a x,y matrix. I then create a new image and paste the results. However, I cannot get the size of the output image to be EXACTLY the height and length of the text that's extracted.
img = Image.open("word.png")
img = img.convert("P")
his = img.histogram()
values = {}
for i in range(256):
values[i] = his[i]
for j,k in sorted(values.items(), key=itemgetter(1), reverse=True)[:10]:
print j,k
img2 = Image.new("P",img.size,255) #THIS SIZE MODE IS WHAT I CAN'T FIX
img = img.convert("P")
temp = {}
for x in range(img.size[1]):
for y in range(img.size[0]):
pix = img.getpixel((y,x))
temp[pix] = pix
if pix == 15 or pix == 11 or pix == 12 or pix == 14:
img2.putpixel((y,x),0)
img2.save("output.png")
In the bit that I have commented:
If I put (0,0) as a blank canvas, it says "image index out of range".
If I put nothing it doesn't work (it needs a size there).
img.size just uses the size from the first img.
If anyone has a better method of doing this whole thing feel free to tell me. All I am trying to do is get the pixel height/length of a word in a specific font+size so I can store it in a database.
Thanks in advance if anyone can help (its indented on this i don't know why it isn't in the code)
The problem you are trying to solve sounds like you want to size your image to a certain size but don't know what that size is in advance.
PIL does not support dynamically sized images, so you would need to create the larger image as you did and then crop it using the img.crop method.
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