Art Grc
Art Grc

Reputation: 573

Serving Images using Tornado without File I/O

I'm trying to serve a webcam image using Tornado library but the only way I found is to save the image first and then return the image name.

Is there a way to serve the image without saving to disk?

import tornado.ioloop
import tornado.web
import pygame.camera
import pygame.image
from time import time
from io import StringIO

pygame.camera.init()
cam = pygame.camera.Camera(pygame.camera.list_cameras()[0])
cam.start()

class MainHandler(tornado.web.RequestHandler):
    def get(self):


        img = cam.get_image()
        name = str( round( time() ) )
        name = name + '.jpg'
        pygame.image.save(img, name)


        self.write('<img src="' + name + '">')



application = tornado.web.Application([
    (r"/", MainHandler),
    (r'/(.*)', tornado.web.StaticFileHandler, {'path': ''})
])

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

Upvotes: 2

Views: 8612

Answers (3)

Tamer Tas
Tamer Tas

Reputation: 3298

Yes, you can serve images without dealing with File I/O. I have a python3 application that tracks the users by sending a 1x1 pixel using Tornado

I store the image as such in the source code:

# 1x1 Transparent Pixel in HEX Format
pixel_GIF = [0x47,0x49,0x46,0x38,0x39,0x61,
             0x01,0x00,0x01,0x00,0x80,0x00,
             0x00,0x00,0x00,0x00,0xff,0xff,
             0xff,0x21,0xf9,0x04,0x01,0x00,
             0x00,0x00,0x00,0x2c,0x00,0x00,
             0x00,0x00,0x01,0x00,0x01,0x00, 
             0x00,0x02,0x01,0x44,0x00,0x3b]

Then, I convert the image in HEX format to binary by using the following function:

def pack_into_binary(data):
    """Convert given data into binary form"""
    packed = str()
    for datum in data:
        packed += struct.pack('B', datum).decode("ISO-8859-1")
    return packed

pixel_binary = pack_into_binary(pixel_GIF)

After that I set the headers appropriately and serve the image:

#1x1 Transparent Tracking Pixel
self.set_header("Content-Length", 42)
self.set_header("Content-Type", "image/gif")
self.set_header("Pragma", "no-cache")
self.set_header("Cache-Control", 
                "no-store, "
                "no-cache=Set-Cookie, "
                "proxy-revalidate, "
                "max-age=0, "
                "post-check=0, pre-check=0"
                )
self.set_header("Expires", "Wed, 2 Dec 1837 21:00:12 GMT")
self.write(self.pixel_binary)

Without doing any File I/O.

Note: In your case if you have the image in memory you can serve it by converting the format to binary and using write method. The difference is your image is not pre-determined unlike mine.

Edit: Check below for the example conversion from RGB Surface to binary encoding

from StringIO import StringIO
from PIL import Image

data = pygame.image.tostring(cam.get_image(),"RGB")

img = Image.fromstring('RGBA', (100,200), data) # 100 x 200 example surface
zdata = StringIO()

img.save(zdata, 'JPEG')

self.write(zdata.getvalue())

Then you can serve image using Tornado

Upvotes: 1

dano
dano

Reputation: 94891

It doesn't look like pygame supports saving an image to a file-like object, so you won't be able to use it directly. However, it does have a tostring method. The documentation for that notes that it allows interopability with other image libraries:

Creates a string that can be transferred with the ‘fromstring’ method in other Python imaging packages

So, you can use tostring to turn your image into a string, then use another Python library which supports writing an image to a file-like object, and use its fromstring method,

Here's an example using pillow as the alternative image library.

import tornado.ioloop
import tornado.web
from PIL import Image
import cStringIO as StringIO

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("<img src='http://localhost:8888/img'>")

class ImgHandler(tornado.web.RequestHandler):
    img_name = "bg.jpg"
    img = pygame.image.load(img_name)
    str_img = pygame.image.tostring(img, "RGB")
    size = img.get_size()
    fimg = Image.frombytes("RGB", size, str_img, "raw")
    fobj = StringIO.StringIO()
    fimg.save(fobj, format="png")  #jpeg encoder isn't available in my install...
    for line in fobj.getvalue():
        self.write(line)
    self.set_header("Content-type",  "image/png")


application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/img", ImgHandler),
    #(r'/(.*)', tornado.web.StaticFileHandler, {'path': ''})
])

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

Both localhost:8888 and localhost:8888/img will display the image.

Upvotes: 4

A. Jesse Jiryu Davis
A. Jesse Jiryu Davis

Reputation: 24007

Sure! Something like:

class ImageHandler(tornado.web.RequestHandler):
    def get(self):
        img = cam.get_image()
        self.set_header('Content-Type', 'image/jpg')
        self.write(img.contents)

application = tornado.web.Application([
    (r"/image.jpg", ImageHandler),
])

I don't know exactly how to get the image's contents as bytes, but you get the idea.

Upvotes: -1

Related Questions