Libvips: web api performance issues

Created on 19 Jan 2018  路  5Comments  路  Source: libvips/libvips

Hi,

I want to use web api for pyvips.
But realy bad performance. Where do I make mistakes?

My code is following;

hello.py

from flask import Flask
from Image import ImageTransform
from flask import request
from flask import send_file
import io

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

@app.route("/image")
def image():
    image_value = request.args.get('image')
    i = ImageTransform(image_value)
    i.set_transform()
    a = i.image.write_to_buffer('.jpeg')
    return send_file(io.BytesIO(a), mimetype='image/jpg')

Image.py

import pyvips
import math

class ImageTransform(object):
    image = None

    def __init__(self, file_name):
        self.image = pyvips.Image.new_from_file(file_name, access='sequential')

    def set_resize_transform(self, width, height):
        im = self.image
        mem = im.write_to_buffer('.jpeg[Q=80]')
        im = pyvips.Image.thumbnail_buffer(mem, width, height=height)

        im = im.bandjoin(255)
        im = im.gravity("centre", width, height,
                            extend="background",
                            background=[244, 244, 244, 255])
        self.image = im                            
        pass
    def set_ratio_and_watermark_transform(self):
        im = self.image
        # ##### ######
        # Trigonometry: Tangent = Opposite / Adjacent
        tangent = im.height/im.width
        # convert arctangent to degrees
        angle = math.atan(tangent)*(180/math.pi);

        water_mark_x = 140
        water_mark_y = 140
        water_mark_len = 16
        # a^2 = b^2 + c^2 ; a = sqrt(b^2 + c^2)
        half_hypotenuse = math.sqrt(im.height*im.height + im.width*im.width)/2;
        font_size = int(math.hypot(im.height, im.width)/math.hypot(water_mark_x, water_mark_y)*(4 + int(water_mark_len/2)))

        text = pyvips.Image.text("text text text", font = "Futura Medium", align='centre', width = 500, dpi = (font_size*4))
        text = text.linear(0.3, 0)

        text = text.similarity(angle=angle)

        x = (im.width-text.width)/2
        y = (im.height-text.height)/2

        text = text.embed(x, y, text.width + x, text.height + y, extend="copy")

        w = im.width if text.width > im.width else text.width
        h = im.height if text.height > im.height else text.height

        im = text.ifthenelse(255, im, blend=True)      
        self.image = im

    def set_transform(self, type=int):
        self.set_ratio_and_watermark_transform()
        self.set_resize_transform(600, 450)

    def write_image(self,dest_name):
        self.image.write_to_file(dest_name)

```bash
FLASK_APP=hello.py flask run

```bash
ab -c 10 -n 10 http://54.229.246.74/image\?image\=foto.jpeg
question

Most helpful comment

I think I'd add the watermark like this:

# in libvips 8.6, you can get text to automatically size to fill a rectangle
text = pyvips.Image.text("text text text", 
                         font="Futura Medium", align="centre", 
                         width=300, height=300)  

# scale down brightness, then back to 8 bit ... this will avoid float arithmetic
# for the rotate/expand/ifthenelse
text = (text * 0.3).cast("uchar")

tangent = float(im.width) / im.height
angle = math.atan(tangent) * (180 / math.pi)
text = text.similarity(angle=angle)
text = text.gravity("centre", target_width, target_height)

im = text.ifthenelse(255, im, blend=True) 

So that's using the new "size text to fit box" feature in 8.6, using x * 0.3 instead of linear (they do the same thing, the operator overload is just easier to read), keeping the text as 8-bit to avoid float later, and using gravity to make the embed a bit simpler.

All 5 comments

Hello @volkan,

I would reorganise your pipeline to something like:

  1. Use thumbnail to open the image and downsize in one operation
  2. Add the watermark overlay
  3. Finally, use gravity to add the background

I'll make an example for you.

Here's a test program that runs your code and a possible new pipeline. I changed the tangent line in your version to:

        tangent = float(im.width) / im.height

But otherwise unaltered.

import time
import sys
import math
import pyvips

from Image import ImageTransform

start = time.time()
i = ImageTransform(sys.argv[1])
i.set_transform()
i.write_image(sys.argv[2])
end = time.time()
print "ImageTransform() took ", end - start

start = time.time()

target_width = 600
target_height = 450         

# efficiently load and downsize in one operation
im = pyvips.Image.thumbnail(sys.argv[1], target_width, height=target_height)

# watermark
tangent = float(im.width) / im.height
angle = math.atan(tangent) * (180 / math.pi)
water_mark_x = 140
water_mark_y = 140
water_mark_len = 16
half_hypotenuse = math.sqrt(im.height*im.height + im.width*im.width)/2;
font_size = int(math.hypot(im.height, im.width)/math.hypot(water_mark_x, water_mark_y)*(4 + int(water_mark_len/2)))
text = pyvips.Image.text("text text text", font = "Futura Medium", align='centre', width = 500, dpi = (font_size*4))
text = text.linear(0.3, 0)
text = text.similarity(angle=angle)
x = (im.width - text.width) / 2
y = (im.height - text.height) / 2
text = text.embed(x, y, text.width + x, text.height + y, extend="copy")
w = im.width if text.width > im.width else text.width
h = im.height if text.height > im.height else text.height
im = text.ifthenelse(255, im, blend=True)  

# add alpha and margins
im = im.bandjoin(255)
im = im.gravity("centre", target_width, target_height,
                    extend="background",
                    background=[244, 244, 244, 255])

im.write_to_file(sys.argv[2])

end = time.time()

print "after revision, took ", end - start

On this laptop with a 6k x 4k image, I see:

$ python volkan.py ~/pics/theo.jpg x.jpg
ImageTransform() took  0.490525960922
after revision, took  0.0774891376495

You could make your watermark code a little simpler, I'll have a go.

I think I'd add the watermark like this:

# in libvips 8.6, you can get text to automatically size to fill a rectangle
text = pyvips.Image.text("text text text", 
                         font="Futura Medium", align="centre", 
                         width=300, height=300)  

# scale down brightness, then back to 8 bit ... this will avoid float arithmetic
# for the rotate/expand/ifthenelse
text = (text * 0.3).cast("uchar")

tangent = float(im.width) / im.height
angle = math.atan(tangent) * (180 / math.pi)
text = text.similarity(angle=angle)
text = text.gravity("centre", target_width, target_height)

im = text.ifthenelse(255, im, blend=True) 

So that's using the new "size text to fit box" feature in 8.6, using x * 0.3 instead of linear (they do the same thing, the operator overload is just easier to read), keeping the text as 8-bit to avoid float later, and using gravity to make the embed a bit simpler.

@jcupitt your examples very helpful. Thanks.

OK, I'll close!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

erdmann picture erdmann  路  4Comments

sonyarianto picture sonyarianto  路  4Comments

kloczek picture kloczek  路  3Comments

AKlein920 picture AKlein920  路  3Comments

sonylifull picture sonylifull  路  4Comments