Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimization encoding in 124 times #25

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
install_requires=[
'cffi',
'Pillow',
'six',
],
setup_requires=[
'cffi',
Expand Down
30 changes: 13 additions & 17 deletions src/blurhash/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
from __future__ import absolute_import
from itertools import chain

from contextlib import nullcontext
from enum import Enum

from PIL import Image

from six.moves import zip
from enum import Enum

from ._functions import ffi as _ffi, lib as _lib
from ._version import version as __version__


__all__ = 'encode', 'decode', 'is_valid_blurhash', 'PixelMode', \
'BlurhashDecodeError', '__version__'
__all__ = ('encode', 'decode', 'is_valid_blurhash', 'PixelMode',
'BlurhashDecodeError', '__version__')


class PixelMode(Enum):
Expand All @@ -30,16 +26,16 @@ def __str__(self):


def encode(image, x_components, y_components):
if not isinstance(image, Image.Image):
if isinstance(image, Image.Image):
image_context = nullcontext()
else:
image = Image.open(image)
if image.mode != 'RGB':
image = image.convert('RGB')
red_band = image.getdata(band=0)
green_band = image.getdata(band=1)
blue_band = image.getdata(band=2)
rgb_data = list(chain.from_iterable(zip(red_band, green_band, blue_band)))
width, height = image.size
image.close()
image_context = image
with image_context:
if image.mode != 'RGB':
image = image.convert('RGB')
rgb_data = image.tobytes()
width, height = image.size

rgb = _ffi.new('uint8_t[]', rgb_data)
bytes_per_row = _ffi.cast('size_t', width * 3)
Expand Down
10 changes: 5 additions & 5 deletions src/common.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#ifndef __BLURHASH_COMMON_H__
#define __BLURHASH_COMMON_H__

#include<math.h>
#include <math.h>

#ifndef M_PI
#define M_PI 3.14159265358979323846
Expand All @@ -13,10 +13,10 @@ static inline int linearTosRGB(float value) {
else return (1.055 * powf(v, 1 / 2.4) - 0.055) * 255 + 0.5;
}

static inline float sRGBToLinear(int value) {
float v = (float)value / 255;
if(v <= 0.04045) return v / 12.92;
else return powf((v + 0.055) / 1.055, 2.4);
static inline float sRGBToLinear(uint8_t value) {
float v = value * (1 / 255.0);
if(v <= 0.04045) return v * (1 / 12.92);
else return powf((v + 0.055) * (1 /1.055), 2.4);
}

static inline float signPow(float value, float exp) {
Expand Down
129 changes: 88 additions & 41 deletions src/encode.c
Original file line number Diff line number Diff line change
@@ -1,49 +1,70 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "common.h"

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

struct RGB {
float r;
float g;
float b;
};

static struct RGB multiplyBasisFunction(int xComponent, int yComponent, int width, int height, uint8_t *rgb, size_t bytesPerRow);
static void multiplyBasisFunction(
float factors[][4], int factorsCount, int width, int height, uint8_t *rgb, size_t bytesPerRow,
float *cosX, float *cosY);
static char *encode_int(int value, int length, char *destination);

static int encodeDC(float r, float g, float b);
static int encodeAC(float r, float g, float b, float maximumValue);

float *sRGBToLinear_cache = NULL;

static void init_sRGBToLinear_cache() {
if (sRGBToLinear_cache != NULL) {
return;
}
sRGBToLinear_cache = (float *)malloc(sizeof(float) * 256);
for (int x = 0; x < 256; x++) {
sRGBToLinear_cache[x] = sRGBToLinear(x);
}
}

const char *blurHashForPixels(int xComponents, int yComponents, int width, int height, uint8_t *rgb, size_t bytesPerRow, char *destination) {
if(xComponents < 1 || xComponents > 9) return NULL;
if(yComponents < 1 || yComponents > 9) return NULL;

#ifndef _MSC_VER
float factors[yComponents * xComponents][3];
#else
float factors[9 * 9][3];
#endif
float factors[yComponents * xComponents][4];
int factorsCount = xComponents * yComponents;
memset(factors, 0, sizeof(factors));

for(int y = 0; y < yComponents; y++) {
init_sRGBToLinear_cache();

float *cosX = (float *)malloc(sizeof(float) * width * factorsCount);
if (! cosX) return NULL;
float *cosY = (float *)malloc(sizeof(float) * height * factorsCount);
if (! cosY) {
free(cosX);
return NULL;
}
for(int i = 0; i < width; i++) {
for(int x = 0; x < xComponents; x++) {
struct RGB factor = multiplyBasisFunction(x, y, width, height, rgb, bytesPerRow);
factors[y * xComponents + x][0] = factor.r;
factors[y * xComponents + x][1] = factor.g;
factors[y * xComponents + x][2] = factor.b;
float weight = cosf(M_PI * x * i / width);
for(int y = 0; y < yComponents; y++) {
cosX[i * factorsCount + y * xComponents + x] = weight;
}
}
}
for(int i = 0; i < height; i++) {
for(int y = 0; y < yComponents; y++) {
float weight = cosf(M_PI * y * i / height);
for(int x = 0; x < xComponents; x++) {
cosY[i * factorsCount + y * xComponents + x] = weight;
}
}
}
multiplyBasisFunction(factors, factorsCount, width, height, rgb, bytesPerRow, cosX, cosY);
free(cosX);
free(cosY);

float *dc = factors[0];
float *ac = dc + 3;
int acCount = xComponents * yComponents - 1;
float *ac = dc + 4;
int acCount = factorsCount - 1;
char *ptr = destination;

int sizeFlag = (xComponents - 1) + (yComponents - 1) * 9;
Expand All @@ -52,7 +73,7 @@ const char *blurHashForPixels(int xComponents, int yComponents, int width, int h
float maximumValue;
if(acCount > 0) {
float actualMaximumValue = 0;
for(int i = 0; i < acCount * 3; i++) {
for(int i = 0; i < acCount * 4; i++) {
actualMaximumValue = fmaxf(fabsf(ac[i]), actualMaximumValue);
}

Expand All @@ -67,34 +88,60 @@ const char *blurHashForPixels(int xComponents, int yComponents, int width, int h
ptr = encode_int(encodeDC(dc[0], dc[1], dc[2]), 4, ptr);

for(int i = 0; i < acCount; i++) {
ptr = encode_int(encodeAC(ac[i * 3 + 0], ac[i * 3 + 1], ac[i * 3 + 2], maximumValue), 2, ptr);
ptr = encode_int(encodeAC(ac[i * 4 + 0], ac[i * 4 + 1], ac[i * 4 + 2], maximumValue), 2, ptr);
}

*ptr = 0;

return destination;
}

static struct RGB multiplyBasisFunction(int xComponent, int yComponent, int width, int height, uint8_t *rgb, size_t bytesPerRow) {
struct RGB result = { 0, 0, 0 };
float normalisation = (xComponent == 0 && yComponent == 0) ? 1 : 2;

static void multiplyBasisFunction(
float factors[][4], int factorsCount, int width, int height, uint8_t *rgb, size_t bytesPerRow,
float *cosX, float *cosY
) {
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
float basis = cosf(M_PI * xComponent * x / width) * cosf(M_PI * yComponent * y / height);
result.r += basis * sRGBToLinear(rgb[3 * x + 0 + y * bytesPerRow]);
result.g += basis * sRGBToLinear(rgb[3 * x + 1 + y * bytesPerRow]);
result.b += basis * sRGBToLinear(rgb[3 * x + 2 + y * bytesPerRow]);
uint8_t *src = rgb + y * bytesPerRow;
float *cosYLocal = cosY + y * factorsCount;
int x = 0;
for(; x < width - 3; x += 4) {
float *cosXLocal = cosX + x * factorsCount;
float pixel0[4] = {sRGBToLinear_cache[src[3 * (x+0) + 0]], sRGBToLinear_cache[src[3 * (x+0) + 1]], sRGBToLinear_cache[src[3 * (x+0) + 2]]};
float pixel1[4] = {sRGBToLinear_cache[src[3 * (x+1) + 0]], sRGBToLinear_cache[src[3 * (x+1) + 1]], sRGBToLinear_cache[src[3 * (x+1) + 2]]};
float pixel2[4] = {sRGBToLinear_cache[src[3 * (x+2) + 0]], sRGBToLinear_cache[src[3 * (x+2) + 1]], sRGBToLinear_cache[src[3 * (x+2) + 2]]};
float pixel3[4] = {sRGBToLinear_cache[src[3 * (x+3) + 0]], sRGBToLinear_cache[src[3 * (x+3) + 1]], sRGBToLinear_cache[src[3 * (x+3) + 2]]};
for (int i = 0; i < factorsCount; i++) {
float basis0 = cosYLocal[i] * cosXLocal[i + 0 * factorsCount];
float basis1 = cosYLocal[i] * cosXLocal[i + 1 * factorsCount];
float basis2 = cosYLocal[i] * cosXLocal[i + 2 * factorsCount];
float basis3 = cosYLocal[i] * cosXLocal[i + 3 * factorsCount];
factors[i][0] += basis0 * pixel0[0] + basis1 * pixel1[0] + basis2 * pixel2[0] + basis3 * pixel3[0];
factors[i][1] += basis0 * pixel0[1] + basis1 * pixel1[1] + basis2 * pixel2[1] + basis3 * pixel3[1];
factors[i][2] += basis0 * pixel0[2] + basis1 * pixel1[2] + basis2 * pixel2[2] + basis3 * pixel3[2];
}
}
for(; x < width; x++) {
float pixel[4];
float *cosXLocal = cosX + x * factorsCount;
pixel[0] = sRGBToLinear_cache[src[3 * x + 0]];
pixel[1] = sRGBToLinear_cache[src[3 * x + 1]];
pixel[2] = sRGBToLinear_cache[src[3 * x + 2]];
for (int i = 0; i < factorsCount; i++) {
float basis = cosYLocal[i] * cosXLocal[i];
factors[i][0] += basis * pixel[0];
factors[i][1] += basis * pixel[1];
factors[i][2] += basis * pixel[2];
}
}
}

float scale = normalisation / (width * height);

result.r *= scale;
result.g *= scale;
result.b *= scale;

return result;
for (int i = 0; i < factorsCount; i++) {
float normalisation = (i == 0) ? 1 : 2;
float scale = normalisation / (width * height);
factors[i][0] *= scale;
factors[i][1] *= scale;
factors[i][2] *= scale;
}
}

static int encodeDC(float r, float g, float b) {
Expand Down
10 changes: 7 additions & 3 deletions tests/test_encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ def test_encode_file():
assert result == 'LlMF%n00%#MwS|WCWEM{R*bbWBbH'


def test_encode_pil_image():
def test_encode_pil_image_twise():
with Image.open('tests/pic2.png') as image:
result = encode(image, 4, 3)
image = image.convert('RGB')
result1 = encode(image, 4, 3)
# Should not raise second time
result2 = encode(image, 4, 3)

assert result == 'LlMF%n00%#MwS|WCWEM{R*bbWBbH'
assert result1 == result2
assert result1 == 'LlMF%n00%#MwS|WCWEM{R*bbWBbH'


def test_encode_with_filename():
Expand Down