Skip to content

Commit

Permalink
Merge pull request #45 from malomalo/quailty
Browse files Browse the repository at this point in the history
Support Quality Operator and Add Newer Image formats
  • Loading branch information
malomalo authored Nov 27, 2023
2 parents dd5df81 + 99fd4dc commit f564668
Show file tree
Hide file tree
Showing 18 changed files with 1,109 additions and 338 deletions.
115 changes: 107 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,117 @@ on:
jobs:
sunstone:
name: BobRoss Test
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
matrix:
vips-version:
- 8.15.0
im-version:
- 7.1.1-21
ruby-version:
- 3.3.0-preview3
- 3.2
- 3.1
backend:
- libvips
- imagemagick

steps:
- run: |
sudo apt-get install libvips imagemagick mupdf-tools
- name: Install Deps
run: |
sudo apt-get -y install \
ffmpeg mupdf-tools \
libtiff-dev \
libpng-dev libpng16-16 \
libjpeg-dev libjpeg-turbo8-dev libjpeg-turbo-progs \
libwebp-dev \
libopenexr-dev \
libopenjp2-7-dev libopenjp2-tools \
libheif-dev \
libexif-dev \
libfreetype6-dev \
libgsf-1-dev libltdl-dev libraw-dev \
meson ninja-build libgirepository1.0-dev
- uses: ruby/setup-ruby@v1
- name: libvips Build Cache
id: vips-cache
uses: actions/cache/restore@v3
with:
path: /home/runner/vips
key: compile-vips-${{ matrix.vips-version }}

- name: Compile Libvips
if: steps.vips-cache.outputs.cache-hit != 'true'
run: |
mkdir -p /home/runner/vips
cd /home/runner/vips
wget --content-disposition --no-verbose 'https://github.com/libvips/libvips/releases/download/v${{ matrix.vips-version }}/vips-${{ matrix.vips-version }}.tar.xz'
tar xf vips-${{ matrix.vips-version }}.tar.xz
cd vips-${{ matrix.vips-version }}
meson setup build-dir --buildtype=release
cd build-dir
meson compile
meson test
- name: Cache libvips Build
if: steps.vips-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v3
with:
path: /home/runner/vips
key: compile-vips-${{ matrix.vips-version }}

- name: Install Libvips
run: |
cd /home/runner/vips/vips-${{ matrix.vips-version }}/build-dir
sudo meson install
- name: ImageMagick Build Cache
id: im-cache
uses: actions/cache/restore@v3
with:
ruby-version: 3.0
path: /home/runner/im
key: compile-im-${{ matrix.im-version }}

- uses: actions/checkout@v2
- name: Compile Imagemagick
if: steps.im-cache.outputs.cache-hit != 'true'
run: |
mkdir -p /home/runner/im
cd /home/runner/im
wget --content-disposition --no-verbose 'https://github.com/ImageMagick/ImageMagick/archive/refs/tags/${{ matrix.im-version }}.tar.gz'
tar xf ImageMagick-${{ matrix.im-version }}.tar.gz
cd ImageMagick-${{ matrix.im-version }}
./configure --with-modules --enable-file-type --with-quantum-depth=16 --with-jpeg=yes --with-png=yes --with-gif=yes --with-webp=yes --with-heic=yes --with-raw=yes --with-tiff=yes --with-openjp2 --with-freetype=yes --with-openexr=yes --with-gslib=yes --with-perl=yes --with-jxl=yes
make
- run: bundle
- name: Cache ImageMagick Build
if: steps.im-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v3
with:
path: /home/runner/im
key: compile-im-${{ matrix.im-version }}

- name: Install Imagemagick
run: |
cd /home/runner/im/ImageMagick-${{ matrix.im-version }}
sudo make install
sudo ldconfig /usr/local/lib
- name: Version Info
run: |
vips --version
identify --version
ffmpeg -version
mutool -v
- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically

- run: bundle exec rake test
- env:
VIPS_WARNING: 1
run: |
bundle exec rake test:${{ matrix.backend }}
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ The BobRoss server depends on the following:
Optionally:

- `libheif` to support the [HEIC](https://en.wikipedia.org/wiki/High_Efficiency_Image_File_Format)
image format.
and [AVIF](https://en.wikipedia.org/wiki/AVIF) image formats.
- `libwebp` to support the [WEBP](https://en.wikipedia.org/wiki/WebP) image
format.
- `jxrlib` to support the [JPEG XR](https://en.wikipedia.org/wiki/JPEG_XR)
- `libjxl` to support the [JPEG XL](https://en.wikipedia.org/wiki/JPEG_XL)
image format.
- `OpenJPEG` to support the [JPEG 2000](https://en.wikipedia.org/wiki/JPEG_2000) image format.
- `giflib` for GIF support in LibVips.
- `libjpeg-turbo` for faster JPEG encoding/decoding.
- The `sqlite3` gem to use a local disk cache.
Expand Down Expand Up @@ -86,8 +87,8 @@ BobRoss.configure({

hmac: 'secret',

# Default is 'imagemagick', you can also pass the class of another backend to use
backend: 'libvips',
# Default is 'libvips', you can also pass the class of another backend to use
backend: 'imagemagick',

# Any other options you wish to apply by default
})
Expand All @@ -114,7 +115,7 @@ end
`last_modified` (if `last_modified_header` is set to true),
`destination` (if local?), and `copy_to_tempfile` (if !local?)

- `backend:` (Optional, default `imagemagick`) `imagemagick` or `libvips`
- `backend:` (Optional, default `libvips`) `imagemagick` or `libvips`
- `memory_limit:` (Optional, ie. `"1GB"`) Limit for max memory that imagemagick will use.
- `disk_limit:` (Optional, ie. `"4GB"`) Limit for max disk that image magick will use
- `hmac:` (Optional)
Expand Down Expand Up @@ -213,9 +214,8 @@ should always comes first.
of combination of the `format`, `hash`, and `transformations` signed with a
shared secret. The server can be configured to only accept certain combinations.

- `I` Interlace or Progressively encodes the image
!!! Place interlace, should look at Line interlacing

- `I` Interlace or Progressively encode the image

- `L` Losslessly encode images.

- `O` - Optimize the image for delivery
Expand Down Expand Up @@ -295,6 +295,10 @@ should always comes first.
required for both. Offsets are not affected by % or other size
operators. Default is `+0+0`

- `Q{quality}` Set the Q-factor for saving the image (1-100; 100 being best quality)

- `R{quality}` Remove any metadata from the image

### The Disk Cache

The local disk cache that is shared across processes. It uses a sqlite3 file to
Expand Down
28 changes: 21 additions & 7 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,24 @@ Bundler.require(:development)
require 'fileutils'
require "rake/testtask"

# Test Task
Rake::TestTask.new do |t|
t.libs << 'lib' << 'test'
t.test_files = FileList[ARGV[1] ? ARGV[1] : 'test/**/*_test.rb']
t.warning = false
# t.verbose = true
end
BACKENDS = %w(libvips imagemagick)

namespace :test do
BACKENDS.each do |backend|
Rake::TestTask.new(backend => "#{backend}:setup") do |t|
t.libs << 'lib' << 'test'
t.test_files = FileList[ARGV[1] ? ARGV[1] : 'test/**/*_test.rb']
t.warning = false
t.verbose = true
end

namespace backend do
task(:setup) { ENV["BOBROSS_BACKEND"] = backend }
end
end

desc "Run test with all backends"
task all: BACKENDS.shuffle.map{ |e| "test:#{e}" }
end

task test: "test:all"
11 changes: 8 additions & 3 deletions lib/bob_ross.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class BobRoss
def initialize
@plugins = {}
@logger = Logger.new(STDOUT)
@transformations = {}
end

def configure(options)
Expand Down Expand Up @@ -61,8 +62,10 @@ def normalize_options(options)
result[:backend] = case options[:backend]
when 'libvips'
BobRoss::LibVipsBackend
else
when 'imagemagick'
BobRoss::ImageMagickBackend
else
BobRoss::LibVipsBackend
end

result
Expand Down Expand Up @@ -190,8 +193,10 @@ def encode_transformations(options = {})
elsif value
transform_options << 'W0se'
end
# when :quality
# post_transform_options << "Q#{value}"
when :quality
post_transform_options << 'Q' + value.to_s
when :strip # Remove Metadata
post_transform_options << 'D'
else
@plugins.values.find do |plugin|
if encode = plugin.encode_transformation(key, value)
Expand Down
65 changes: 55 additions & 10 deletions lib/bob_ross/backends/imagemagick.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,36 @@ module BobRoss::ImageMagickBackend
extend BobRoss::BackendHelpers

class <<self

def key
:im
end

def version
return @version if @version
version_cmd = Terrapin::CommandLine.new("identify", '-version')
@version = version_cmd.run.match(/Version: ImageMagick (\S+)/)[1]
end

def supports?(*mimes)
(mimes - supported_formats).empty?
end

def supported_formats
return @supported_formats if @supported_formats

formats_cmd = Terrapin::CommandLine.new("identify", '-list format')

@supported_formats = formats_cmd.run.gsub(/\s{10,}[^\n]+(?=\n)/, '').lines[2..-6].reduce([]) do |memo, line|
line = line.split(/\s+/).select { |c| !c.empty? }
if line[2][1] == 'w'
mime = MiniMime.lookup_by_extension(line[0].delete_suffix('*').downcase)
memo << mime.content_type if mime
end
memo
end
end

def default_args(options)
params = []
params << "-limit memory :memory_limit" if options[:memory_limit]
Expand All @@ -24,7 +54,7 @@ def identify(path)
output = command.run(file: path)
ident[:opaque] = output.match(/^Opaque:\s(true|false)\s*$/i)[1] == 'True'
ident[:geometry] = parse_geometry(output.match(/^Geometry:\s([0-9x\-\+]+)\s*$/i)[1])
orientation = output.match(/^Orientation:\s(\d)\s*$/i)
orientation = output.match(/^Orientation:\s+(\d)/i)
ident[:orientation] = orientation[1].to_i if orientation
ident
end
Expand Down Expand Up @@ -58,19 +88,34 @@ def transform(image, transformations, options)
end
end

case options[:format]
when 'image/avif'
params << "-quality 45" unless options[:quality]
when 'image/heic'
params << "-quality 40" unless options[:quality]
when 'image/webp'
params << "-quality 45" unless options[:quality]
when 'image/jp2'
params << "-quality 40" unless options[:quality]
when 'image/jpeg'
params << "-quality 43" unless options[:quality]
params << "-define jpeg:optimize-coding=on"
when 'image/png'
params << "-define png:compression-level=9"
end

options.each do |key, value|
case key
when :quality
params << "-quality " << if %w(image/jpeg image/jp2 image/heif image/avif).include?(options[:format])
[[1, value.to_i].max, 100].min.to_s
else
value.to_i
end
when :strip
params << "-strip"
when :lossless
params << "-define webp:lossless=true"
when :optimize
params << "-quality 85" unless options[:format] == 'image/webp'
params << "-define png:compression-filter=5"
params << "-define png:compression-level=9"
params << "-define png:compression-strategy=1"
params << "-define png:exclude-chunk=all"
params << "-interlace none" unless options[:interlace]
params << "-colorspace sRGB"
params << "-strip"
when :interlace
params << "-interlace Plane"
end
Expand Down
Loading

0 comments on commit f564668

Please sign in to comment.