Browse Source

Добавлен модуль для обработки изображений utils.images

В модуль добавлен объект ImageMagick для получения размеров изображения,
изменения размеров изображения для формата backgrounds, с поддержкой
запуска через chroot.
master
parent
commit
933ab35b1c
8 changed files with 327 additions and 0 deletions
  1. +92
    -0
      calculate/utils/images.py
  2. +16
    -0
      conftest.py
  3. +3
    -0
      pytest.ini
  4. +215
    -0
      tests/utils/test_images.py
  5. BIN
      tests/utils/testfiles/file.jpg
  6. BIN
      tests/utils/testfiles/file.png
  7. BIN
      tests/utils/testfiles/origin.png
  8. +1
    -0
      tests/utils/testfiles/wrong.jpg

+ 92
- 0
calculate/utils/images.py View File

@@ -0,0 +1,92 @@
import os
import hashlib
from calculate.utils.files import Process, write_file, read_file

class ImageMagickError(Exception):
pass

class ImageMagick:
def __init__(self, prefix='/'):
self.prefix = prefix
self.init_commands(prefix)
self.default_opts = []

@property
def available(self):
return self.convert_cmd and self.identify_cmd

@property
def chroot(self):
return self.prefix != '/'

def init_commands(self, prefix):
self.convert_cmd = "/usr/bin/convert"
self.identify_cmd = "/usr/bin/identify"
self.chroot_cmd = "/bin/chroot"
self.bash_cmd = "/bin/bash"
if not os.path.exists(os.path.join(prefix, self.convert_cmd[1:])):
self.convert_cmd = None
if not os.path.exists(os.path.join(prefix, self.identify_cmd[1:])):
self.identify_cmd = None

def trim_prefix_path(self, filename):
retpath = "/%s" % os.path.relpath(filename, self.prefix)
if retpath.startswith("/.."):
return None
return retpath

def get_image_resolution(self, source):
if self.chroot:
identify = Process(self.chroot_cmd, self.prefix,
self.bash_cmd, "-c",
" ".join([self.identify_cmd,
"-format '%w %h'", source]))
else:
identify = Process(self.identify_cmd, "-format", "%w %h", source)
if identify.success():
swidth, _sep, sheight = identify.read().strip().partition(" ")
if swidth.isdigit() and sheight.isdigit():
return int(swidth), int(sheight)
return None

def convert(self, source, target, *opts):
command = [self.convert_cmd, "-quality", "95",
source]
command.extend(self.default_opts)
command.extend(opts)
command.append(target)
if self.chroot:
convert = Process(self.chroot_cmd, self.prefix,
self.bash_cmd, "-c",
" ".join(command))
else:
convert = Process(*command)
if convert.success():
return True
else:
print(convert.read_error())
return False

def convert_resize_crop_center(self, source, target, height, width):
#if ((width == self.source_width and height == self.source_height) and
# (source.rpartition('.')[2] == target.rpartition('.')[2])):
# with write_file(target) as sf:
# sf.write(read_file(source))
# return True
res = "%dx%d" % (width, height)

return self.convert(source, target, "-quality", "95",
"-resize", "%s^" % res,
"-strip", "-gravity", "center",
"-crop", "%s+0+0" % res)

def convert_resize_gfxboot(self, source, target, height, width):
res = "%dx%d" % (width, height)

return self.convert(source, target, "-quality", "95",
"-resize", "%s^" % res,
"-strip", "-gravity", "center",
"-crop", "%s+0+0" % res,
"-sampling-factor", "2x2",
"-interlace", "none",
"-set", "units", "PixelsPerSecond")

+ 16
- 0
conftest.py View File

@@ -162,3 +162,19 @@ def DictionariesWithoutSections():
ResultDictionary = OrderedDict(**ParamLine1, **ParamLine3)

return (OriginalDictionary, TemplateDictionary, ResultDictionary)

import pytest


def pytest_addoption(parser):
parser.addoption(
"--chroot-test", action="store_true", default=False, help="run chroot tests"
)

def pytest_collection_modifyitems(config, items):
if config.getoption("--chroot-test"):
return
skip_chroot = pytest.mark.skip(reason="need --chroot option to run")
for item in items:
if "chroot" in item.keywords:
item.add_marker(skip_chroot)

+ 3
- 0
pytest.ini View File

@@ -24,6 +24,7 @@ markers =

files_utils: marker for running tests for calculate.utils.files module.
package_utils: marker for running tests for calculate.utils.contents module.
images_utils: marker for running tests for calculate.utils.images module.
gentoo: marker for running tests for utils.gentoo
calculateini: marker for running tests for utils.calculateini
@@ -41,3 +42,5 @@ markers =
scripts: marker for testing of the scripts.
commands: marker for testing of the commands.
server: marker for testing of the server.

chroot: marker for testing running by chroot

+ 215
- 0
tests/utils/test_images.py View File

@@ -0,0 +1,215 @@
import pytest
from calculate.utils.images import ImageMagick, ImageMagickError
import os
from calculate.utils.files import Process

@pytest.mark.images_utils
def test_imagemagick_initialization():
im = ImageMagick()
assert im.available
assert not im.chroot
im = ImageMagick(prefix='/mnt/somepath')
assert not im.available
assert im.chroot


@pytest.mark.images_utils
@pytest.mark.parametrize('case',
[
{
"name": "simple path",
"chroot": "/mnt/install",
"source": "/mnt/install/usr/share/pixmap/image.jpg",
"result": "/usr/share/pixmap/image.jpg",
},
{
"name": "relative",
"chroot": "/mnt/install",
"source": "/mnt/install/usr/share/../pixmap/image.jpg",
"result": "/usr/pixmap/image.jpg",
},
{
"name": "first level",
"chroot": "/mnt/install",
"source": "/mnt/install/image.jpg",
"result": "/image.jpg",
},
{
"name": "Wrong",
"chroot": "/mnt/install",
"source": "/mnt/image.jpg",
"result": None,
},
],
ids=lambda x:x["name"])
def test_imagemagick_trim_prefix_path(case):
im = ImageMagick(case['chroot'])
assert im.trim_prefix_path(case["source"]) == case["result"]

@pytest.mark.images_utils
@pytest.mark.parametrize('case',
[
{
"name": "PNG file",
"image": "tests/utils/testfiles/file.png",
"result": (48,48),
},
{
"name": "JPEG file",
"image": "tests/utils/testfiles/file.jpg",
"result": (320,180),
},
{
"name": "No file",
"image": "tests/utils/testfiles/file2.jpg",
"result": None,
},
{
"name": "Wrong file",
"image": "tests/utils/testfiles/wrong.jpg",
"result": None,
},
],
ids=lambda x:x["name"])
def test_imagemagick_get_resolutions(case):
im = ImageMagick()
assert im.get_image_resolution(case["image"]) == case["result"]

@pytest.fixture
def chroot_test():
chrootpath = "/mnt/testchroot"
assert os.getuid() == 0, "Need superuser privileges"
if os.path.exists(chrootpath):
os.unlink(chrootpath)
os.symlink("/", chrootpath)
try:
yield chrootpath
finally:
os.unlink(chrootpath)

@pytest.mark.chroot
@pytest.mark.images_utils
def test_chroot_imagemagick_get_resolution(chroot_test):
im = ImageMagick(chroot_test)
curpath = os.getcwd()
image_path = os.path.join(curpath, "tests/utils/testfiles/file.png")
assert im.get_image_resolution(image_path) == (48,48)

def get_histogram(image, remap_image):
p = Process("/usr/bin/convert", image, "-remap", remap_image, "-format", "%c", "histogram:info:-")
return p.read()

def get_verbose_image_info(image):
p = Process("/usr/bin/identify", "-verbose", image)
return p.read()

@pytest.mark.images_utils
@pytest.mark.parametrize('case',
[
{
# проверка, пропорционального уменьшения
"name": "Origin test",
"resize": (16,32),
"result": """192: (0,0,0) #000000 black
64: (0,255,0) #00FF00 lime
256: (255,255,255) #FFFFFF white"""
},
{
# проверка, что при изменении размера только по горизонтали
# удаляются только части изображения справа и слева
# в исходном изображении на этих частях находится белый фон
"name": "Shrink horizontal",
"resize": (16,16),
"result": """192: (0,0,0) #000000 black
64: (0,255,0) #00FF00 lime"""
},
{
# проверка, что при уменьшении изображения первоначально оно сдавливается
# по вертикали а затем обрезаются части слева и справа
"name": "Shrink all",
"resize": (8,8),
"result": """48: (0,0,0) #000000 black
16: (0,255,0) #00FF00 lime"""
},
{
# проверка, пропорционального уменьшения
"name": "Shrink proportionately",
"resize": (8,16),
"result": """48: (0,0,0) #000000 black
16: (0,255,0) #00FF00 lime
64: (255,255,255) #FFFFFF white"""
},
{
# проверка, пропорционального увеличения
"name": "Increase size proportionately",
"resize": (32,64),
"result": """768: (0,0,0) #000000 black
256: (0,255,0) #00FF00 lime
1024: (255,255,255) #FFFFFF white"""
},
{
# проверка увеличения и обрезки по горизонтали
"name": "Increase size and cut",
"resize": (32,32),
"result": """768: (0,0,0) #000000 black
256: (0,255,0) #00FF00 lime"""
},
{
# проверка увеличения по горизонтали
# в этом случае будет отрезан верх и низ
# поэтому на выходе нет зелёного цвета
"name": "Increase horizontal size",
"resize": (16,48),
"result": """384: (0,0,0) #000000 gray(0)
384: (255,255,255) #FFFFFF gray(255)"""
},
],
ids=lambda x:x["name"])
def test_imagemagick_convert(case):
image_path = "tests/utils/testfiles/origin.png"
output_file = "tests/utils/testfiles/test_output5.png"
if os.path.exists(output_file):
os.unlink(output_file)
im = ImageMagick()
im.default_opts = ["-filter","box"]
assert im.convert_resize_crop_center(image_path, output_file, *case["resize"])
histogram = get_histogram(output_file, image_path)
discard_space = lambda x: x.replace(" ","").replace("\n","")
assert discard_space(histogram) == discard_space(case["result"])

@pytest.mark.chroot
@pytest.mark.images_utils
def test_chroot_imagemagick_convert_center(chroot_test):
curpath = os.getcwd()
image_path = "tests/utils/testfiles/origin.png"
output_file = "tests/utils/testfiles/test_output.png"
image_path = os.path.join(curpath, image_path)
output_file = os.path.join(curpath, output_file)
result = """48: (0,0,0) #000000 black
16: (0,255,0) #00FF00 lime"""
if os.path.exists(output_file):
os.unlink(output_file)
im = ImageMagick(chroot_test)
im.default_opts = ["-filter","box"]
assert im.convert_resize_crop_center(image_path, output_file, 8, 8)
histogram = get_histogram(output_file, image_path)
discard_space = lambda x: x.replace(" ","").replace("\n","")
assert discard_space(histogram) == discard_space(result)

@pytest.mark.images_utils
def test_imagemagick_convert_gfxboot():
output_file = "tests/utils/testfiles/test_output.jpg"
image_path = "tests/utils/testfiles/origin.png"
im = ImageMagick()
im.convert_resize_gfxboot(image_path, output_file, 32, 32)
assert "sampling-factor: 2x2" in get_verbose_image_info(output_file)

@pytest.mark.images_utils
def test_clear_imagemagick_convert():
for output_file in (
"tests/utils/testfiles/test_output.png",
"tests/utils/testfiles/test_output.jpg"
):
if os.path.exists(output_file):
os.unlink(output_file)

BIN
tests/utils/testfiles/file.jpg View File

Before After
Width: 320  |  Height: 180  |  Size: 13 KiB

BIN
tests/utils/testfiles/file.png View File

Before After
Width: 48  |  Height: 48  |  Size: 4.5 KiB

BIN
tests/utils/testfiles/origin.png View File

Before After
Width: 32  |  Height: 16  |  Size: 5.4 KiB

+ 1
- 0
tests/utils/testfiles/wrong.jpg View File

@@ -0,0 +1 @@
NOFILE

Loading…
Cancel
Save