Skip to content

Commit

Permalink
DRIFT-524: Replace boost by CImg (#32)
Browse files Browse the repository at this point in the history
* Add CImg usage

* fix python test, etc

* fix conanfile dependencies

* Update CHANGELOG

* Fix benchmark fixtures
  • Loading branch information
victor1234 authored Sep 6, 2022
1 parent 18438b9 commit b1d889f
Show file tree
Hide file tree
Showing 12 changed files with 117 additions and 93 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ jobs:
if : matrix.os == 'ubuntu-20.04'
run: conan profile update settings.compiler.libcxx=libstdc++11 default

- name: Build boost
run: conan install boost/1.73.0@ --build missing

- name: Build package for stable channel
if: github.ref_type == 'tag'
env:
Expand Down
5 changes: 5 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ repos:
rev: 0.8.1
hooks:
- id: gersemi

- repo: https://github.com/psf/black
rev: 22.6.0
hooks:
- id: black
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@

* Dependencies to CI, [PR-30](https://github.com/panda-official/WaveletBuffer/pull/30)

### Changed

* DRIFT-524: Replace boost by CImg, [PR-32](https://github.com/panda-official/WaveletBuffer/pull/32)

### Fixed

* DRIFT-524: Fix benchmark fixtures, [PR-32](https://github.com/panda-official/WaveletBuffer/pull/32)
* Build boost/1.73 package, [PR-30](https://github.com/panda-official/WaveletBuffer/pull/30)

## 0.3.0 - 2022-08-15
Expand Down
17 changes: 14 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,15 @@ else()
conan_cmake_configure(REQUIRES openblas/0.3.20
blaze/3.8
libjpeg-turbo/2.1.3
boost/1.73.0
cimg/3.0.2
OPTIONS
cimg:enable_fftw=False
cimg:enable_jpeg=False # To avoid conflict with libjpeg-turbo
cimg:enable_openexr=False
cimg:enable_png=False
cimg:enable_tiff=False
cimg:enable_ffmpeg=False
cimg:enable_opencv=False
GENERATORS CMakeDeps
)
conan_cmake_autodetect(settings)
Expand All @@ -52,7 +60,7 @@ endif()
find_package(blaze REQUIRED)
find_package(OpenBLAS REQUIRED)
find_package(libjpeg-turbo REQUIRED)
find_package(Boost REQUIRED)
find_package(cimg REQUIRED)

# Dependencies
include(FetchContent)
Expand Down Expand Up @@ -93,6 +101,9 @@ target_compile_definitions(
PUBLIC BLAZE_USE_SHARED_MEMORY_PARALLELIZATION=0
)

# Set CImg options
target_compile_definitions(cimg::cimg INTERFACE cimg_display=0)

# Set compiler features
target_compile_features(${WB_TARGET_NAME} PUBLIC cxx_std_20)
set_target_properties(${WB_TARGET_NAME} PROPERTIES CXX_STANDARD_REQUIRED ON)
Expand All @@ -113,7 +124,7 @@ target_link_libraries(${WB_TARGET_NAME} sf_compressor)
target_link_libraries(${WB_TARGET_NAME} blaze::blaze)
target_link_libraries(${WB_TARGET_NAME} OpenBLAS::OpenBLAS)
target_link_libraries(${WB_TARGET_NAME} libjpeg-turbo::libjpeg-turbo)
target_link_libraries(${WB_TARGET_NAME} Boost::headers)
target_link_libraries(${WB_TARGET_NAME} cimg::cimg)

# Catch2 installation
if(WB_BUILD_TESTS OR WB_BUILD_BENCHMARKS)
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,3 @@ target_link_libraries(program wavelet_buffer::wavelet_buffer)
find_package(LAPACK REQUIRED)
target_link_libraries(program ${LAPACK_LIBRARIES})
```

Binary file modified benchmarks/img/fixtures/pic100x100.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified benchmarks/img/fixtures/pic400x400.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions benchmarks/img/jpeg_codec_benchmarks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ TEST_CASE("JPEG codec benchmarks") {
return ss.str();
};

auto pic_100_x_100 = load_picture(kPic100x100);
auto pic_200_x_200 = load_picture(kPic200x200);
auto pic_400_x_400 = load_picture(kPic400x400);
const std::string pic_100_x_100 = load_picture(kPic100x100);
const std::string pic_200_x_200 = load_picture(kPic200x200);
const std::string pic_400_x_400 = load_picture(kPic400x400);

HslJpegCodec codec;

Expand Down
18 changes: 15 additions & 3 deletions conan/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,22 @@ class WaveletBufferConan(ConanFile):
author = "PANDA GmbH"
description = "An universal C++ compression library based on wavelet transformation"
url = "https://github.com/panda-official/WaveletBuffer"
requires = "openblas/0.3.20", "blaze/3.8", "libjpeg-turbo/2.1.2", "boost/1.73.0"
requires = "openblas/0.3.20", "blaze/3.8", "libjpeg-turbo/2.1.2", "cimg/3.0.2"
default_options = {
"cimg:enable_fftw": False,
"cimg:enable_jpeg": False,
"cimg:enable_openexr": False,
"cimg:enable_png": False,
"cimg:enable_tiff": False,
"cimg:enable_ffmpeg": False,
"cimg:enable_opencv": False,
"shared": False,
"fPIC": True,
}

# Binary configuration
settings = "os", "compiler", "build_type", "arch"
options = {"shared": [True, False], "fPIC": [True, False]}
default_options = {"shared": False, "fPIC": True}

generators = "CMakeDeps"

Expand All @@ -39,7 +49,9 @@ def source(self):
if local_source is not None:
print(f"Use local sources: {local_source}")
self.run(
"cp -r {}/. {}/".format(os.getenv("CONAN_SOURCE_DIR"), self.source_folder)
"cp -r {}/. {}/".format(
os.getenv("CONAN_SOURCE_DIR"), self.source_folder
)
)
else:
branch = f"v{self.version}" if self.channel == "stable" else self.channel
Expand Down
2 changes: 1 addition & 1 deletion python/tests/test_codecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def test__encode(self, cls): # pylint: disable=no-self-use
dtype=np.single,
)
data: bytes = codec.encode(image, 1)
assert len(data) > 1000
assert len(data) > 300

pil_image: Image = Image.open(io.BytesIO(data), formats=("JPEG",))
assert pil_image
Expand Down
149 changes: 74 additions & 75 deletions sources/img/jpeg_codecs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@

#include "wavelet_buffer/img/jpeg_codecs.h"

#include <jpeglib.h>
#include <jerror.h>
#include <cstdio>

#include <iostream>
#include <string>

#include <boost/gil/extension/io/jpeg.hpp>
#include <boost/gil/image.hpp>
#include <boost/gil/typedefs.hpp>

#define cimg_plugin "plugins/jpeg_buffer.h"
#include "CImg.h"

#include "wavelet_buffer/img/color_space.h"

namespace drift::img {

namespace bg = boost::gil;
using cimg_library::CImg;

void CheckRangeQuality(DataType quality) {
if ((quality < 0) || (quality > 1.f)) {
Expand All @@ -35,35 +39,35 @@ RgbJpegCodec::RgbJpegCodec(DataType quality) : quality_(quality) {

bool RgbJpegCodec::Decode(const std::string& blob, SignalN2D* image,
size_t start_channel) const {
bg::rgb8_image_t bg_image;
std::stringstream in_buffer(blob, std::ios_base::in | std::ios_base::binary);
if (blob.empty()) {
std::cerr << "Failed to decode image: buffer is empty" << std::endl;
return false;
}

CImg<unsigned char> img;
try {
bg::read_image(in_buffer, bg_image, bg::jpeg_tag());
img.load_jpeg_buffer(reinterpret_cast<const JOCTET*>(blob.data()),
blob.size());
} catch (std::exception& e) {
std::cerr << "Failed to decode image: " << e.what() << std::endl;
return false;
}

const auto rows = bg_image.height();
const auto columns = bg_image.width();
const auto rows = img.height();
const auto columns = img.width();

auto& im = *image;
if (im.size() < start_channel + 3) {
im.resize(start_channel + 3);
}

auto rgb_view = bg::view(bg_image);
for (int ch = start_channel; ch < start_channel + 3; ++ch) {
for (size_t ch = start_channel; ch < start_channel + 3; ++ch) {
im[ch] = Signal2D(rows, columns);
const auto& view_channel =
bg::nth_channel_view(rgb_view, ch - start_channel);
auto it_channel = view_channel.begin();

for (int i = 0; i < rows; i++) {
auto it_channel_end = it_channel + columns;
std::transform(it_channel, it_channel_end, im[ch].begin(i),
[](auto x) { return static_cast<DataType>(x) / 255; });
it_channel = it_channel_end;

for (size_t i = 0; i < rows; ++i) {
for (size_t j = 0; j < columns; ++j) {
im[ch](i, j) = img(j, i, ch - start_channel) / 255.;
}
}
}

Expand All @@ -76,26 +80,23 @@ bool RgbJpegCodec::Encode(const SignalN2D& image, std::string* blob,
return false;
}

const auto rows = image[start_channel].rows();
const auto columns = image[start_channel].columns();

bg::rgb8_image_t rgb_img(columns, rows);
auto rgb_view = bg::view(rgb_img);
for (int c = start_channel; c < start_channel + 3; c++) {
const auto& view_channel =
bg::nth_channel_view(rgb_view, c - start_channel);
auto it = view_channel.begin();
for (int i = 0; i < rows; i++) {
it = std::transform(image[c].begin(i), image[c].end(i), it,
[](auto x) { return ToPixel(x); });
const size_t rows = image[start_channel].rows();
const size_t columns = image[start_channel].columns();

CImg<unsigned char> img(columns, rows, 1, 3);
for (size_t c = start_channel; c < start_channel + 3; c++) {
for (size_t i = 0; i < rows; i++) {
for (size_t j = 0; j < columns; j++) {
img(j, i, c - start_channel) = ToPixel(image[c](i, j));
}
}
}
unsigned int buffer_size = image.size() * rows * columns + 1000;
blob->resize(buffer_size);
img.save_jpeg_buffer(reinterpret_cast<JOCTET*>(blob->data()), buffer_size,
static_cast<int>(quality_ * 100));
blob->resize(buffer_size);

std::stringstream out_buffer(std::ios_base::out | std::ios_base::binary);
bg::write_view(out_buffer, rgb_view,
bg::image_write_info<bg::jpeg_tag>(quality_ * 100));

*blob = out_buffer.str();
return true;
}

Expand Down Expand Up @@ -187,36 +188,39 @@ GrayJpegCodec::GrayJpegCodec(DataType quality) : quality_(quality) {
CheckRangeQuality(quality);
}

bool GrayJpegCodec::Decode(const std::string& blob, SignalN2D* img,
bool GrayJpegCodec::Decode(const std::string& blob, SignalN2D* image,
size_t start_channel) const {
bg::image_read_settings<bg::jpeg_tag> read_settings;
bg::gray8_image_t bg_image;
// bg::image_read_settings<bg::jpeg_tag> read_settings;
// bg::gray8_image_t bg_image;

static_assert(
bg::num_channels<decltype(bg_image)::view_t::value_type>::value == 1,
"Image must have 1 channels");
if (blob.empty()) {
std::cerr << "Failed to decode image: buffer is empty" << std::endl;
return false;
}

CImg<unsigned char> img;
try {
std::istringstream iss(blob);
bg::read_image(iss, bg_image, read_settings);
img.load_jpeg_buffer(reinterpret_cast<const JOCTET*>(blob.data()),
blob.size());
} catch (std::exception& e) {
std::cerr << "Failed to decode image: " << e.what() << std::endl;
return false;
}

const auto& view = bg::view(bg_image);
const auto rows = img.height();
const auto columns = img.width();

auto& im = *img;
if (im.size() < 1 + start_channel) {
im.resize(start_channel + 1);
}
im[start_channel] = Signal2D(view.height(), view.width());
auto& im = *image;
if (im.size() < start_channel + 1) {
im.resize(start_channel + 1);
}

for (size_t y = 0; y < view.height(); ++y) {
for (size_t x = 0; x < view.width(); ++x) {
const auto& p = view(x, y);
im[start_channel](y, x) = static_cast<DataType>(p) / 255;
}
im[start_channel] = Signal2D(img.height(), img.width());

for (size_t i = 0; i < rows; ++i) {
for (size_t j = 0; j < columns; ++j) {
im[start_channel](i, j) = img(j, i, 0) / 255.;
}
} catch (std::exception const& e) {
std::cerr << "Decode exception: " << e.what() << std::endl;
return false;
}

return true;
Expand All @@ -228,26 +232,21 @@ bool GrayJpegCodec::Encode(const SignalN2D& image, std::string* blob,
return false;
}

try {
bg::gray8_image_t gil_img(image[start_channel].columns(),
image[start_channel].rows());
auto& view = bg::view(gil_img);
const size_t columns = image[start_channel].columns();
const size_t rows = image[start_channel].rows();

for (size_t y = 0; y < view.height(); ++y) {
for (size_t x = 0; x < view.width(); ++x) {
view(x, y) = ToPixel(image[start_channel](y, x));
}
CImg<unsigned char> img(columns, rows, 1, 1);
for (size_t i = 0; i < rows; i++) {
for (size_t j = 0; j < columns; j++) {
img(j, i, 0) = ToPixel(image[start_channel](i, j));
}
std::stringstream write_stream;
bg::write_view(write_stream, view,
bg::image_write_info<bg::jpeg_tag>(quality_ * 100));
*blob = std::string((std::istreambuf_iterator<char>(write_stream)),
std::istreambuf_iterator<char>());
} catch (std::exception const& e) {
std::cerr << "Encode exception: " << e.what() << std::endl;
return false;
}

unsigned int buffer_size = image.size() * rows * columns + 1000;
blob->resize(buffer_size);
img.save_jpeg_buffer(reinterpret_cast<JOCTET*>(blob->data()), buffer_size,
static_cast<int>(quality_ * 100));
blob->resize(buffer_size);
return true;
}

Expand Down
4 changes: 0 additions & 4 deletions wavelet_buffer/img/jpeg_codecs.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@
#include <sstream>
#include <string>

#include <boost/gil/extension/io/jpeg.hpp>
#include <boost/gil/image.hpp>
#include <boost/gil/typedefs.hpp>

#include "wavelet_buffer/img/color_space.h"
#include "wavelet_buffer/img/image_codec.h"
#include "wavelet_buffer/primitives.h"
Expand Down

0 comments on commit b1d889f

Please sign in to comment.