Reading an image into pygame with incorrect file extension - python

I want to load a JPS file into pygame. A JPS file is simply a jpeg file with the image consisting of two side by side stereo pictures. While Pigame will load it in if I change the extension to jpg and use pygame.image.load(file_name), what I want to do is to load the file into memory and then tell Pigame to load the file in from a buffer and that the buffer contains a jpeg file.
I would like to do it this way because later I want to extend things so that I can load in an MPO file which is a file that contains two jpeg files and I suspect that the same techniques will be involved.
I have tried the pygame.image.frombuffer and pygame.image.fromstring but get the error message that the "String length does not equal format and resolution size". I think this is because I am not telling it that the buffer contains a jpeg.
Any one have any idea how this can be done?

Perhaps something along these lines (untested):
with open('image.jps', 'rb') as imgfile:
imgbuf = StringIO(imgfile.read())
image1 = pygame.image.load(imgbuf)
Since you say it works, you could probably shorten things as shown below since there's no reason to give the image buffer a name and keep it around:
with open('image.jps', 'rb') as imgfile:
image1 = pygame.image.load(StringIO(imgfile.read()))

Related

Save JPEG comment using Pillow

I need to save an Image in Python (created as a Numpy array) as a JPEG file, while including a "comment" in the file with some specific metadata. This metadata will be used by another (third-party) application and is a simple ASCII string. I have a sample image including such a "comment", which I can read out using Pillow (PIL), via the image.info['comment'] or the image.app['COM'] property. However, when I try a simple round-trip, i.e. loading my sample image and save it again using a different file name, the comment is no longer preserved. Equally, I found no way to include a comment in a newly created image.
I am aware that EXIF tags are the preferred way to save metadata in JPEG images, but as mentioned, the third-party application only accepts this data as a "comment", not as EXIF, which I cannot change. After reading this question, I looked into the binary structure of my sample file and found the comment at the start of the file, after a few bytes of some other (meta)data. I do however not know a lot about binary file manipulation, and also I was wondering if there is a more elegant way, other than messing with the binary...
EDIT: minimum example:
from PIL import Image
img = Image.open(path) # where path is the path to the sample image
# this prints the desired metadata if it is correctly saved in loaded image
print(img.info["comment"])
img.save(new_path) # save with different file name
img.close()
# now open to see if it has been saved correctly
new_img = Image.open(new_path)
print(new_img.info['comment']) # now results in KeyError
I also tried img.save(new_path, info=img.info), but this does not seem to have an effect. Since img.info['comment'] appears identical to img.app['COM'], I tried img.save(new_path, app=img.app), again does not work.
Just been having a play with this and I couldn't see anything directly in Pillow to support this. I've found that the save() method supports a parameter called extra that can be used to pass arbitrary bytes to the output file.
We then just need a simple method to turn a comment into a valid JPEG segment, for example:
import struct
from PIL import Image
def make_jpeg_variable_segment(marker: int, payload: bytes) -> bytes:
"make a JPEG segment from the given payload"
return struct.pack('>HH', marker, 2 + len(payload)) + payload
def make_jpeg_comment_segment(comment: bytes) -> bytes:
"make a JPEG comment/COM segment"
return make_jpeg_variable_segment(0xFFFE, comment)
# open source image
with Image.open("foo.jpeg") as im:
# save out with new JPEG comment
im.save('bar.jpeg', extra=make_jpeg_comment_segment("hello world".encode()))
# read file back in to ensure comment round-trips
with Image.open('bar.jpeg') as im:
print(im.app['COM'])
print(im.info['comment'])
Note that in my initial attempts I tried appending the comment segment at the end of the file, but Pillow wouldn't load this comment even after calling the .load() method to force it to load the entire JPEG file.
Update: The upcoming version Pillow version 9.4.0 will support this by passing a comment parameter while saving, e.g.:
with Image.open("foo.jpeg") as im:
im.save('bar.jpeg', comment="hello world")
hopefully that makes things easier!

OpenCV whole Images to bytes without Saving to Disk

Basically, I want to add a few bytes to my new PNG image file. An example case is like the following code:
img = open("example.png", "rb") # Open images and read as binnary
hex = img.read().hex() # Read images as Bytes to Hexadecimal.
add_hex = hex+"7feab1e74a4bdb755cca" # Add some bytes to it (as hex)
to_bytes_img = bytes.fromhex(add_hex) # Convert hex to bytes
with open("example2.png", "wb") as f: # Write images
f.write(to_bytes_img)
But, the problem is, I have a special case that requires me to perform the above operation using OpenCV (CV2). Where cv2.imread() only reads and stores Pixels as a numpy array (an array of pixels, not the whole file).
Then, I want to write that image into a new file cv2.imwrite(), which will rebuild the image and save the PNG on disk. My question is, how do I add some bytes to the PNG image file (in buffer/memory), before the cv2.imwrite() operation.
I could probably do it with with open() as above, but that would be very inefficient opening, writing, opening, writing to disk again.

Errno 20: Not a directory when saving into zip file

When I try to save a pyplot figure as a jpg, I keep getting a directory error saying that the given file name is not a directory. I am working in Colab. I have a numpy array called z_img and have opened a zip file.
import matplotlib.pyplot as plt
from zipfile import ZipFile
zipObj = ZipFile('slices.zip', 'w') # opening zip file
plt.imshow(z_img, cmap='binary')
The plotting works fine. I did a test of saving the image into Colab's regular memory like so:
plt.savefig(str(ii)+'um_slice.jpg')
And this works perfectly, except I am intending to use this code in a for loop. ii is an index to differentiate between each image, and several hundred images would be created so I want them going in the zipfile. Now when I try adding the path to the zipfile:
plt.savefig('/content/slices.zip/'+str(ii)+'um_slice.jpg')
I get: NotADirectoryError: [Errno 20] Not a directory: '/content/slices.zip/150500um_slice.jpg'
I assume it's because the {}.jpg string is a filename, and not a directory per se. But I am quite new to Python, and don't know how to get the plot into the zip file. That's all I want. Would love any advice!
First off, for anything that's not photographic content (ie. nice and soft), JPEG is the wrong format. You'll have a better time using a different file format. PNG is nice for pixels, SVG for vector graphics (in case you embed this in a website later!), PDF for vector, too.
The error message is quite on point: you cannot just save to a zip file as if it was a directory.
Multiple ways around:
use the tempfile module's mkdtemp to make a temporary directory, save into that, and zip the result
save not into a filename, but into a buffer (BytesIO I guess) and append that to the compressed stream (I'm not too familiar with ZipFile)
use PDF as output and simply generate a multipage PDF; it's not hard, and probably much nicer in the long term. You can still convert that vector graphic result to PNG (or any other pixel format9 as desired, but for the time being, it's space efficient, arbitrarily scaleable and keeps all your pages in one place. It's easy to import selected pages into LaTeX (matter of fact, \includegraphics does it directly) or into websites (pdf.js).
From the docs, matplotlib.pyplot.savefig accepts a binary file-like object. ZipFile.open creates binary file like objects. These two have to get todgether!
with zipobj.open(str(ii)+'um_slice.jpg', 'w') as fp:
plt.savefig(fp)

working with .bmp files in python 3

I have a bmp file. It is just a red square. I have to write a program with functions to make it have white stripes. Things I would need to do:
load the bmp file.
read and assess the bmp file.
code certain areas coordinates of the file to be colored white.
close the file
display the end product file as output
i am a novice, and am having trouble reading or displaying the original bmp file, let alone edit the content inside. it is not similar to opening a txt file and "readline()". also, when i copy paste the bmp file in the pydev projects src folder in eclipse, it does not show up on eclipse, so i don't know if how the computer would recognize that the file is there. i want to read up on it before posting here, but i don't seem to get much results googling, since i am not sure exactly what i should search for.
The easy way to do this is with a third-party image-processing library like PIL/Pillow. The code is simple enough that you could figure it out in a few minutes from the examples on the Image module docs…
But if you're not allowed to do that, let's look at how to do this manually.
First, BMP isn't a text file format, it's a binary format. That means you have to read it in binary mode. And you can't read it "line by line", because it doesn't have lines of text to read. Since a bytes object isn't mutable, you will probably want to copy it into a bytearray to work with. So:
with open('spam.bmp', 'rb') as f:
data = bytearray(f.read())
Next, you need to parse the BMP file format. I assume the main point of the exercise is figuring out how to do that yourself, so I'll give you a link to Wikipedia's article, which describes it better than the Microsoft docs, and you can go from there.
The struct module in the standard library will be very helpful for interpreting the headers; it's much easier to read a 32-bit little-endian number with struct.unpack_from('<L', data, offset) than with by reading data[offset], data[offset+1], etc. and re-combining them into a 32-bit number.
I'm guessing you can ignore all the options for BMP compression—otherwise, this would be way too hard an assignment. In fact, you can probably just assume that all of the headers will specify the most common variant and only code for that. But you might want to ask your teacher for feedback on that.
Now, once you've found the "pixel array" portion of the BMP, and you've figured out how to interpret it from the DIB header, you can just set pixels to white at whichever positions you want by setting the values at the appropriate indexes of the bytearray. For example, it may turn out to be as simple as:
pos = pixel_array_offset + row_size * y + pixel_size * x
data[pos:pos+3] = 255, 255, 255
Finally, once you've changed your red pixels to white, you can save it with:
with open('eggs.bmp', 'wb') as f:
f.write(data)

Python/Pygame Converting a .jpg to a string and back to a .jpg: Corruption Issue

I'm making a program in Python using Pygame that will load an image to the screen, open the raw data (as in, the characters you would see if you opened the jpg as a text file), throw some random characters in with the data, and then resave it as a jpg to load into pygame again. This results in a cool looking glitch effect.
I am not having any problems with the desired glitches, but I was finding that despite what kind of random character was placed where, for certain images every time the image went through my glitch function I ended up with a grey bar on the bottom of the image. I simplified my function so that all it did was load the image, open the image as a read binary (even though I'm on a mac), save a string of the raw data, write a new file based on this string and then load that file. The image was not purposefully glitched in any way, and the data was supposedly untouched but I still encountered this grey bar.
Here is the relevant code:
def initializeScreen(x, y):
pygame.display.set_mode((x,y))
return pygame.display.get_surface()
def importImage(fileName):
imgText = open(fileName, 'rb')
imgTextStr = imgText.read()
imgText.close()
return imgTextStr
screenSurf = initializeScreen(800,600)
textOfImg = importImage('/Users/Amoeba/Desktop/GlitchDriving/Clouds.jpg')
newFile = open('/Users/Amoeba/Desktop/GlitchDriving/tempGlitchFile.jpg', 'wb')
newFile.write(textOfImg)
newimgSurf = pygame.image.load('/Users/Amoeba/Desktop/GlitchDriving/tempGlitchFile.jpg')
screenSurf.blit(newimgSurf, (0,0))
pygame.display.flip()
Here is an example of one of the images before and after passing through my function:
It is worth noting that the size of the grey bar depends on the picture. Some pictures even pass through my function visibly unchanged, as they should be. Also, if I open the new version of the jpg written by my program with image viewing software like preview, the grey bar does not appear. My suspicion is that it is a quirk of the pygame image load function or that there is some strange character (or possibly white space) that is being dropped in my conversion from jpg to string or vice-versa. I did compare two of the text files (one with grey bar and one without) and found no difference while using an online "difference finder".
This is my first post here, but I've lurked for answers dozens of times. Any help is greatly appreciated.
You never close the file object you create with open, so probably not all data gets written back (flushed) to your new file.
Either close the file object before trying to read the file again, or better start using the with statement (which will close the file for you) whenever you deal with files:
def importImage(fileName):
with open(fileName, 'rb') as imgText:
return imgText.read()
screenSurf = initializeScreen(800,600)
textOfImg = importImage(r'path/to/file')
with open(r'path/to/otherfile', 'wb') as newFile:
newFile.write(textOfImg)
newimgSurf = pygame.image.load(r'path/to/otherfile')
screenSurf.blit(newimgSurf, (0,0))
pygame.display.flip()

Categories