assembly-image 1.9.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +3 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +3 -3
- data/.rubocop_todo.yml +17 -7
- data/README.md +3 -24
- data/assembly-image.gemspec +3 -2
- data/lib/assembly-image/image.rb +2 -12
- data/lib/assembly-image/images.rb +0 -5
- data/lib/assembly-image/jp2_creator.rb +13 -65
- data/lib/assembly-image.rb +3 -0
- data/profiles/cmyk.icc +0 -0
- data/spec/assembly/image/jp2_creator_spec.rb +3 -7
- data/spec/image_spec.rb +142 -97
- data/spec/images_spec.rb +38 -38
- data/spec/spec_helper.rb +117 -14
- data/spec/test_data/color_cmyk_tagged.tif +0 -0
- data/spec/test_data/color_cmyk_untagged.tif +0 -0
- data/spec/test_data/color_rgb_adobergb1998_lzw.tif +0 -0
- data/spec/test_data/color_rgb_srgb.jpg +0 -0
- data/spec/test_data/color_rgb_srgb.tif +0 -0
- data/spec/test_data/color_rgb_srgb_rot90cw.tif +0 -0
- data/spec/test_data/color_rgb_untagged.tif +0 -0
- data/spec/test_data/gray_gray_untagged.tif +0 -0
- metadata +40 -10
- data/bin/run_all_tests +0 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9379ec53a26a08578c11910512bd73045366fc337fcafb0c7eb1a247370566b1
|
|
4
|
+
data.tar.gz: 316fbef312cb027f4061bbe74dc2f8dce993a095d6779da100abeeecbf24b9da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 97cc720649bdc6e0bb6d7453afdefc7abe3f0931a45976d397f1f77fd2f2dacbcbd4991e64c0e4849a2ae61efa51d72d428ce9a8a15e9a125abdeefcb17b0d5b
|
|
7
|
+
data.tar.gz: 8e6580aaade8302319e7cbf41d56d8ac0ebe1a05db5607bed8580cebe8a3c87b5795cb90f50e2b9eb4b081d9048a600799b0e373badf811ff9a084e5f9af3fcc
|
data/.circleci/config.yml
CHANGED
|
@@ -12,6 +12,9 @@ workflows:
|
|
|
12
12
|
- run:
|
|
13
13
|
name: Install exiftool
|
|
14
14
|
command: curl -L http://cpanmin.us | perl - --sudo Image::ExifTool
|
|
15
|
+
- run:
|
|
16
|
+
name: Install libvips
|
|
17
|
+
command: sudo apt-get update && sudo apt-get install -y libvips
|
|
15
18
|
- setup_remote_docker
|
|
16
19
|
- run:
|
|
17
20
|
name: Install Kakadu
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -22,9 +22,6 @@ Naming/FileName:
|
|
|
22
22
|
Exclude:
|
|
23
23
|
- lib/assembly-image.rb
|
|
24
24
|
|
|
25
|
-
RSpec/ExampleLength:
|
|
26
|
-
Max: 12
|
|
27
|
-
|
|
28
25
|
Style/WordArray:
|
|
29
26
|
Enabled: false
|
|
30
27
|
|
|
@@ -166,3 +163,6 @@ RSpec/ChangeByZero: # new in 2.11.0
|
|
|
166
163
|
Enabled: true
|
|
167
164
|
RSpec/VerifiedDoubleReference: # new in 2.10.0
|
|
168
165
|
Enabled: true
|
|
166
|
+
|
|
167
|
+
Gemspec/DeprecatedAttributeAssignment: # new in 1.30
|
|
168
|
+
Enabled: true
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on 2022-
|
|
3
|
+
# on 2022-06-16 22:15:07 UTC using RuboCop version 1.30.1.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
@@ -13,13 +13,23 @@ Gemspec/RequiredRubyVersion:
|
|
|
13
13
|
Exclude:
|
|
14
14
|
- 'assembly-image.gemspec'
|
|
15
15
|
|
|
16
|
-
# Offense count:
|
|
17
|
-
#
|
|
16
|
+
# Offense count: 2
|
|
17
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
18
18
|
Lint/RedundantCopDisableDirective:
|
|
19
19
|
Exclude:
|
|
20
20
|
- 'lib/assembly-image/image.rb'
|
|
21
21
|
- 'lib/assembly-image/images.rb'
|
|
22
22
|
|
|
23
|
+
# Offense count: 2
|
|
24
|
+
# Configuration parameters: IgnoredMethods, CountRepeatedAttributes.
|
|
25
|
+
Metrics/AbcSize:
|
|
26
|
+
Max: 20
|
|
27
|
+
|
|
28
|
+
# Offense count: 14
|
|
29
|
+
# Configuration parameters: CountAsOne.
|
|
30
|
+
RSpec/ExampleLength:
|
|
31
|
+
Max: 15
|
|
32
|
+
|
|
23
33
|
# Offense count: 2
|
|
24
34
|
# Configuration parameters: Include, CustomTransform, IgnoreMethods, SpecSuffixOnly.
|
|
25
35
|
# Include: **/*_spec*rb*, **/spec/**/*
|
|
@@ -30,7 +40,7 @@ RSpec/FilePath:
|
|
|
30
40
|
|
|
31
41
|
# Offense count: 21
|
|
32
42
|
RSpec/MultipleExpectations:
|
|
33
|
-
Max:
|
|
43
|
+
Max: 13
|
|
34
44
|
|
|
35
45
|
# Offense count: 5
|
|
36
46
|
RSpec/UnspecifiedException:
|
|
@@ -44,7 +54,7 @@ Style/CombinableLoops:
|
|
|
44
54
|
- 'spec/images_spec.rb'
|
|
45
55
|
|
|
46
56
|
# Offense count: 1
|
|
47
|
-
#
|
|
57
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
48
58
|
Style/GlobalStdStream:
|
|
49
59
|
Exclude:
|
|
50
60
|
- 'lib/assembly-image/images.rb'
|
|
@@ -56,8 +66,8 @@ Style/OptionalBooleanParameter:
|
|
|
56
66
|
Exclude:
|
|
57
67
|
- 'lib/assembly-image/image.rb'
|
|
58
68
|
|
|
59
|
-
# Offense count:
|
|
60
|
-
#
|
|
69
|
+
# Offense count: 7
|
|
70
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
61
71
|
# Configuration parameters: Mode.
|
|
62
72
|
Style/StringConcatenation:
|
|
63
73
|
Exclude:
|
data/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
This gem contains classes used by the Stanford University Digital Library to
|
|
10
|
-
perform image operations necessary for accessioning of content.
|
|
10
|
+
perform image operations necessary for accessioning of content.
|
|
11
11
|
|
|
12
12
|
Requires image processing software - see PreRequisites section below.
|
|
13
13
|
|
|
@@ -69,33 +69,12 @@ http://kakadusoftware.com/downloads/
|
|
|
69
69
|
|
|
70
70
|
NOTE: If you have upgrade to El Capitan on OS X, you will need to donwload and re-install the latest version of Kakadu, due to changes made with SIP. These changes moved the old executable binaries to an inaccessible location.
|
|
71
71
|
|
|
72
|
-
###
|
|
73
|
-
|
|
74
|
-
#### RHEL 6
|
|
75
|
-
|
|
76
|
-
The version of ImageMagick included with RHEL 6 has all of the dependency libraries included:
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
yum install ImageMagick
|
|
80
|
-
```
|
|
81
|
-
#### RHEL 5
|
|
82
|
-
|
|
83
|
-
The version of ImageMagick included with RHEL 5 is too old and does not have all the proper binaries included/built:
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
yum install lcms lcms-devel libjpeg libjpeg-devel libpng libpng-devel
|
|
87
|
-
```
|
|
88
|
-
Required libraries from source:
|
|
89
|
-
* libtiff (version 3.9.4 or higher)
|
|
90
|
-
|
|
91
|
-
Build Imagemagick from source:
|
|
92
|
-
http://www.imagemagick.org/download/ImageMagick.tar.gz
|
|
72
|
+
### Libvips
|
|
93
73
|
|
|
94
74
|
#### Mac
|
|
95
75
|
|
|
96
76
|
```bash
|
|
97
|
-
brew install
|
|
98
|
-
brew install imagemagick --use-tiff --use-jpeg2000
|
|
77
|
+
brew install libvips
|
|
99
78
|
```
|
|
100
79
|
|
|
101
80
|
### Exiftool
|
data/assembly-image.gemspec
CHANGED
|
@@ -4,7 +4,7 @@ $LOAD_PATH.push File.expand_path('lib', __dir__)
|
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |s|
|
|
6
6
|
s.name = 'assembly-image'
|
|
7
|
-
s.version = '
|
|
7
|
+
s.version = '2.0.0'
|
|
8
8
|
s.authors = ['Peter Mangiafico', 'Renzo Sanchez-Silva', 'Monty Hindman', 'Tony Calavano']
|
|
9
9
|
s.email = ['pmangiafico@stanford.edu']
|
|
10
10
|
s.homepage = ''
|
|
@@ -13,14 +13,15 @@ Gem::Specification.new do |s|
|
|
|
13
13
|
s.metadata['rubygems_mfa_required'] = 'true'
|
|
14
14
|
|
|
15
15
|
s.files = `git ls-files`.split("\n")
|
|
16
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
17
16
|
s.bindir = 'exe'
|
|
18
17
|
s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
18
|
s.require_paths = ['lib']
|
|
20
19
|
|
|
21
20
|
s.add_dependency 'assembly-objectfile', '>= 1.6.4'
|
|
22
21
|
s.add_dependency 'mini_exiftool', '>= 1.6', '< 3'
|
|
22
|
+
s.add_dependency 'ruby-vips', '>= 2.0'
|
|
23
23
|
|
|
24
|
+
s.add_development_dependency 'pry-byebug'
|
|
24
25
|
s.add_development_dependency 'rake'
|
|
25
26
|
s.add_development_dependency 'rspec', '~> 3.0'
|
|
26
27
|
s.add_development_dependency 'rubocop'
|
data/lib/assembly-image/image.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'ruby-vips'
|
|
3
4
|
require 'assembly-objectfile'
|
|
4
5
|
require_relative 'jp2_creator'
|
|
5
6
|
|
|
@@ -74,8 +75,7 @@ module Assembly
|
|
|
74
75
|
def add_exif_profile_description(profile_name, force = false)
|
|
75
76
|
if profile.nil? || force
|
|
76
77
|
input_profile = profile_name.gsub(/[^[:alnum:]]/, '') # remove all non alpha-numeric characters, so we can get to a filename
|
|
77
|
-
|
|
78
|
-
input_profile_file = File.join(path_to_profiles, "#{input_profile}.icc")
|
|
78
|
+
input_profile_file = File.join(PATH_TO_PROFILES, "#{input_profile}.icc")
|
|
79
79
|
command = "exiftool '-icc_profile<=#{input_profile_file}' #{path}"
|
|
80
80
|
result = `#{command} 2>&1`
|
|
81
81
|
raise "profile addition command failed: #{command} with result #{result}" unless $CHILD_STATUS.success?
|
|
@@ -94,16 +94,6 @@ module Assembly
|
|
|
94
94
|
File.extname(path).empty? ? "#{path}.jp2" : path.gsub(File.extname(path), '.jp2')
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
-
# Returns the full DPG equivalent jp2 path and filename that would match with the given image
|
|
98
|
-
#
|
|
99
|
-
# @return [string] full DPG equivalent jp2 path and filename
|
|
100
|
-
# Example:
|
|
101
|
-
# source_img=Assembly::Image.new('/input/path_to_file.tif')
|
|
102
|
-
# puts source_img.jp2_filename # gives /input/path_to_file.jp2
|
|
103
|
-
def dpg_jp2_filename
|
|
104
|
-
jp2_filename.gsub('_00_', '_05_')
|
|
105
|
-
end
|
|
106
|
-
|
|
107
97
|
# Create a JP2 file for the current image.
|
|
108
98
|
# Important note: this will not work for multipage TIFFs.
|
|
109
99
|
#
|
|
@@ -37,8 +37,6 @@ module Assembly
|
|
|
37
37
|
|
|
38
38
|
raise 'Input path does not exist' unless File.directory?(source)
|
|
39
39
|
|
|
40
|
-
logger.debug "Source: #{source}"
|
|
41
|
-
|
|
42
40
|
# iterate over input directory looking for tifs
|
|
43
41
|
pattern = recursive ? "**/*.#{extension}" : "*.#{extension}*"
|
|
44
42
|
Dir.glob(File.join(source, pattern)).each do |file|
|
|
@@ -81,9 +79,6 @@ module Assembly
|
|
|
81
79
|
Dir.mkdir(output) unless File.directory?(output) # attemp to make output directory
|
|
82
80
|
raise 'Output path does not exist or could not be created' unless File.directory?(output)
|
|
83
81
|
|
|
84
|
-
logger.debug "Source: #{source}"
|
|
85
|
-
logger.debug "Destination: #{output}"
|
|
86
|
-
|
|
87
82
|
pattern = recursive ? "**/*.#{extension}" : "*.#{extension}*"
|
|
88
83
|
|
|
89
84
|
# iterate over input directory looking for tifs
|
|
@@ -7,7 +7,7 @@ require 'English' # see https://github.com/rubocop-hq/rubocop/issues/1747 (not #
|
|
|
7
7
|
module Assembly
|
|
8
8
|
class Image < Assembly::ObjectFile
|
|
9
9
|
# Creates jp2 derivatives
|
|
10
|
-
class Jp2Creator
|
|
10
|
+
class Jp2Creator
|
|
11
11
|
# Create a JP2 file for the current image.
|
|
12
12
|
# Important note: this will not work for multipage TIFFs.
|
|
13
13
|
#
|
|
@@ -68,6 +68,7 @@ module Assembly
|
|
|
68
68
|
|
|
69
69
|
def jp2_create_command(source_path:, output:)
|
|
70
70
|
options = []
|
|
71
|
+
# TODO: Consider using ruby-vips to determine the colorspace instead of relying on exif (which is done below)
|
|
71
72
|
options << '-jp2_space sRGB' if image.samples_per_pixel == 3
|
|
72
73
|
options += KDU_COMPRESS_DEFAULT_OPTIONS
|
|
73
74
|
options << "Clayers=#{layers}"
|
|
@@ -82,20 +83,15 @@ module Assembly
|
|
|
82
83
|
|
|
83
84
|
KDU_COMPRESS_DEFAULT_OPTIONS = [
|
|
84
85
|
'-num_threads 2', # forces Kakadu to only use 2 threads
|
|
85
|
-
'-precise', # forces the use of 32-bit representations
|
|
86
|
-
'-no_weights', # minimization of the MSE over all reconstructed colour components
|
|
87
86
|
'-quiet', # suppress informative messages.
|
|
88
87
|
'Creversible=no', # Disable reversible compression
|
|
89
|
-
'Cmodes=BYPASS', #
|
|
90
88
|
'Corder=RPCL', # R=resolution P=position C=component L=layer
|
|
91
89
|
'Cblk=\\{64,64\\}', # code-block dimensions; 64x64 happens to also be the default
|
|
92
90
|
'Cprecincts=\\{256,256\\},\\{256,256\\},\\{128,128\\}', # Precinct dimensions; 256x256 for the 2 highest resolution levels, defaults to 128x128 for the rest
|
|
93
|
-
'
|
|
94
|
-
'-rate 1.5', # Ratio of compressed bits to the image size
|
|
91
|
+
'-rate -', # Ratio of compressed bits to the image size
|
|
95
92
|
'Clevels=5' # Number of wavelet decomposition levels, or stages
|
|
96
93
|
].freeze
|
|
97
94
|
|
|
98
|
-
# rubocop:disable Metrics/AbcSize
|
|
99
95
|
def create_jp2_checks
|
|
100
96
|
image.send(:check_for_file)
|
|
101
97
|
raise 'input file is not a valid image, or is the wrong mimetype' unless image.jp2able?
|
|
@@ -104,46 +100,13 @@ module Assembly
|
|
|
104
100
|
raise SecurityError, 'cannot recreate jp2 over itself' if overwrite? && image.mimetype == 'image/jp2' && output_path == image.path
|
|
105
101
|
end
|
|
106
102
|
|
|
107
|
-
# rubocop:disable Metrics/MethodLength
|
|
108
|
-
def profile_conversion_switch(profile, tmp_folder:)
|
|
109
|
-
path_to_profiles = File.join(Assembly::PATH_TO_IMAGE_GEM, 'profiles')
|
|
110
|
-
# eventually we may allow the user to specify the output_profile...when we do, you can just uncomment this code
|
|
111
|
-
# and update the tests that check for this
|
|
112
|
-
output_profile = 'sRGBIEC6196621' # params[:output_profile] || 'sRGBIEC6196621'
|
|
113
|
-
output_profile_file = File.join(path_to_profiles, "#{output_profile}.icc")
|
|
114
|
-
|
|
115
|
-
raise "output profile #{output_profile} invalid" unless File.exist?(output_profile_file)
|
|
116
|
-
|
|
117
|
-
return '' if image.profile.nil?
|
|
118
|
-
|
|
119
|
-
# if the input color profile exists, contract paths to the profile and setup the command
|
|
120
|
-
|
|
121
|
-
input_profile = profile.gsub(/[^[:alnum:]]/, '') # remove all non alpha-numeric characters, so we can get to a filename
|
|
122
|
-
|
|
123
|
-
# construct a path to the input profile, which might exist either in the gem itself or in the tmp folder
|
|
124
|
-
input_profile_file_gem = File.join(path_to_profiles, "#{input_profile}.icc")
|
|
125
|
-
input_profile_file_tmp = File.join(tmp_folder, "#{input_profile}.icc")
|
|
126
|
-
input_profile_file = File.exist?(input_profile_file_gem) ? input_profile_file_gem : input_profile_file_tmp
|
|
127
|
-
|
|
128
|
-
# if input profile was extracted and does not matches an existing known profile either in the gem or in the tmp folder,
|
|
129
|
-
# we'll issue an imagicmagick command to extract the profile to the tmp folder
|
|
130
|
-
unless File.exist?(input_profile_file)
|
|
131
|
-
input_profile_extract_command = "MAGICK_TEMPORARY_PATH=#{tmp_folder} convert '#{image.path}'[0] #{input_profile_file}" # extract profile from input image
|
|
132
|
-
result = `#{input_profile_extract_command} 2>&1`
|
|
133
|
-
raise "input profile extraction command failed: #{input_profile_extract_command} with result #{result}" unless $CHILD_STATUS.success?
|
|
134
|
-
# if extraction failed or we cannot write the file, throw exception
|
|
135
|
-
raise 'input profile is not a known profile and could not be extracted from input file' unless File.exist?(input_profile_file)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
"-profile #{input_profile_file} -profile #{output_profile_file}"
|
|
139
|
-
end
|
|
140
|
-
|
|
141
103
|
# Bigtiff needs to be used if size of image exceeds 2^32 bytes.
|
|
142
104
|
def need_bigtiff?
|
|
143
105
|
image.image_data_size >= 2**32
|
|
144
106
|
end
|
|
145
107
|
|
|
146
108
|
# We do this because we need to reliably compress the tiff and KDUcompress doesn’t support arbitrary image types
|
|
109
|
+
# rubocop:disable Metrics/MethodLength
|
|
147
110
|
def make_tmp_tiff(tmp_folder: nil)
|
|
148
111
|
tmp_folder ||= Dir.tmpdir
|
|
149
112
|
raise "tmp_folder #{tmp_folder} does not exists" unless File.exist?(tmp_folder)
|
|
@@ -151,35 +114,20 @@ module Assembly
|
|
|
151
114
|
# make temp tiff filename
|
|
152
115
|
tmp_tiff_file = Tempfile.new(['assembly-image', '.tif'], tmp_folder)
|
|
153
116
|
tmp_path = tmp_tiff_file.path
|
|
117
|
+
vips_image = Vips::Image.new_from_file image.path
|
|
154
118
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
options << '-type TrueColor'
|
|
163
|
-
when 1
|
|
164
|
-
options << '-depth 8' # force the production of a grayscale access derivative
|
|
165
|
-
options << '-type Grayscale'
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
options << profile_conversion_switch(image.profile, tmp_folder: tmp_folder)
|
|
169
|
-
|
|
170
|
-
# The output in the covnert command needs to be prefixed by the image type. By default ImageMagick
|
|
171
|
-
# will assume TIFF: when the file extension is .tif/.tiff. TIFF64: Needs to be forced when image will
|
|
172
|
-
# exceed 2^32 bytes in size
|
|
173
|
-
tiff_type = need_bigtiff? ? 'TIFF64:' : ''
|
|
174
|
-
|
|
175
|
-
tiff_command = "MAGICK_TEMPORARY_PATH=#{tmp_folder} convert -quiet -compress none #{options.join(' ')} '#{image.path}[0]' #{tiff_type}'#{tmp_path}'"
|
|
176
|
-
result = `#{tiff_command} 2>&1`
|
|
177
|
-
raise "tiff convert command failed: #{tiff_command} with result #{result}" unless $CHILD_STATUS.success?
|
|
119
|
+
vips_image = if vips_image.interpretation.eql?(:cmyk)
|
|
120
|
+
vips_image.icc_transform(SRGB_ICC, input_profile: CMYK_ICC)
|
|
121
|
+
elsif !image.profile.nil?
|
|
122
|
+
vips_image.icc_transform(SRGB_ICC, embedded: true)
|
|
123
|
+
else
|
|
124
|
+
vips_image
|
|
125
|
+
end
|
|
178
126
|
|
|
127
|
+
vips_image.tiffsave(tmp_path, bigtiff: need_bigtiff?)
|
|
179
128
|
tmp_path
|
|
180
129
|
end
|
|
181
130
|
# rubocop:enable Metrics/MethodLength
|
|
182
|
-
# rubocop:enable Metrics/AbcSize
|
|
183
131
|
end
|
|
184
132
|
end
|
|
185
133
|
end
|
data/lib/assembly-image.rb
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
module Assembly
|
|
4
4
|
# the path to the gem, used to access profiles stored with the gem
|
|
5
5
|
PATH_TO_IMAGE_GEM = File.expand_path(File.dirname(__FILE__) + '/..')
|
|
6
|
+
PATH_TO_PROFILES = File.join(Assembly::PATH_TO_IMAGE_GEM, 'profiles')
|
|
7
|
+
SRGB_ICC = File.join(PATH_TO_PROFILES, 'sRGBIEC6196621.icc')
|
|
8
|
+
CMYK_ICC = File.join(PATH_TO_PROFILES, 'cmyk.icc')
|
|
6
9
|
end
|
|
7
10
|
|
|
8
11
|
require 'assembly-image/image'
|
data/profiles/cmyk.icc
ADDED
|
Binary file
|
|
@@ -9,11 +9,7 @@ RSpec.describe Assembly::Image::Jp2Creator do
|
|
|
9
9
|
let(:input_path) { TEST_TIF_INPUT_FILE }
|
|
10
10
|
let(:creator) { described_class.new(ai, output: TEST_JP2_OUTPUT_FILE) }
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
# after each test, empty out the input and output test directories
|
|
14
|
-
remove_files(TEST_INPUT_DIR)
|
|
15
|
-
remove_files(TEST_OUTPUT_DIR)
|
|
16
|
-
end
|
|
12
|
+
before { cleanup }
|
|
17
13
|
|
|
18
14
|
context 'when given an LZW compressed RGB tif' do
|
|
19
15
|
before do
|
|
@@ -31,8 +27,8 @@ RSpec.describe Assembly::Image::Jp2Creator do
|
|
|
31
27
|
expect(creator.tmp_path).not_to be_nil
|
|
32
28
|
expect(result.exif.colorspace).to eq 'sRGB'
|
|
33
29
|
jp2 = Assembly::Image.new(TEST_JP2_OUTPUT_FILE)
|
|
34
|
-
expect(jp2.height).to eq
|
|
35
|
-
expect(jp2.width).to eq
|
|
30
|
+
expect(jp2.height).to eq 36
|
|
31
|
+
expect(jp2.width).to eq 43
|
|
36
32
|
end
|
|
37
33
|
end
|
|
38
34
|
|
data/spec/image_spec.rb
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
|
+
require 'fileutils'
|
|
4
5
|
|
|
5
6
|
RSpec.describe Assembly::Image do
|
|
6
7
|
let(:assembly_image) { described_class.new(input_path) }
|
|
7
8
|
let(:input_path) { TEST_TIF_INPUT_FILE }
|
|
9
|
+
let(:jp2_output_file) { File.join(TEST_OUTPUT_DIR, File.basename(input_path).gsub('.tif', '.jp2')) }
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
# after each test, empty out the input and output test directories
|
|
11
|
-
remove_files(TEST_INPUT_DIR)
|
|
12
|
-
remove_files(TEST_OUTPUT_DIR)
|
|
13
|
-
end
|
|
11
|
+
before { cleanup }
|
|
14
12
|
|
|
15
13
|
describe '#jp2_filename' do
|
|
16
14
|
it 'indicates the default jp2 filename' do
|
|
17
|
-
expect(assembly_image.jp2_filename).to eq
|
|
15
|
+
expect(assembly_image.jp2_filename).to eq input_path.gsub('.tif', '.jp2')
|
|
18
16
|
end
|
|
19
17
|
|
|
20
18
|
context 'with a file with no extension' do
|
|
@@ -26,200 +24,247 @@ RSpec.describe Assembly::Image do
|
|
|
26
24
|
end
|
|
27
25
|
end
|
|
28
26
|
|
|
29
|
-
describe '#dpg_jp2_filename' do
|
|
30
|
-
context 'with a dpg tiff file' do
|
|
31
|
-
let(:input_path) { TEST_DPG_TIF_INPUT_FILE }
|
|
32
|
-
|
|
33
|
-
it 'indicates the default DPG jp2 filename' do
|
|
34
|
-
expect(assembly_image.dpg_jp2_filename).to eq TEST_DPG_TIF_INPUT_FILE.gsub('.tif', '.jp2').gsub('_00_', '_05_')
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
context 'with a file with no extension' do
|
|
39
|
-
let(:input_path) { '/path/to/a/file_with_no_00_extension' }
|
|
40
|
-
|
|
41
|
-
it 'indicates the default jp2 filename' do
|
|
42
|
-
expect(assembly_image.dpg_jp2_filename).to eq '/path/to/a/file_with_no_05_extension.jp2'
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
27
|
describe '#create_jp2' do
|
|
48
28
|
context 'when input path is blank' do
|
|
49
29
|
let(:input_path) { '' }
|
|
50
30
|
|
|
51
31
|
it 'does not run if no input file is passed in' do
|
|
52
|
-
expect { assembly_image.create_jp2 }.to raise_error
|
|
32
|
+
expect { assembly_image.create_jp2 }.to raise_error(RuntimeError)
|
|
53
33
|
end
|
|
54
34
|
end
|
|
55
35
|
|
|
56
36
|
context 'when given an uncompressed compressed RGB tif with more than 4GB of image data', skip: 'This test will create a 4GB test image and a 4GB temporary image, so skipping by default.' do
|
|
37
|
+
let(:input_path) { File.join(TEST_INPUT_DIR, 'rgb.tif') }
|
|
38
|
+
|
|
57
39
|
before do
|
|
58
|
-
generate_test_image(
|
|
40
|
+
generate_test_image(input_path, compress: 'none', width: '37838', height: '37838')
|
|
59
41
|
end
|
|
60
42
|
|
|
61
43
|
it 'creates the jp2 with a temp file' do
|
|
62
|
-
expect(File).to exist
|
|
63
|
-
expect(File).not_to exist
|
|
64
|
-
result = assembly_image.create_jp2(output:
|
|
44
|
+
expect(File).to exist input_path
|
|
45
|
+
expect(File).not_to exist jp2_output_file
|
|
46
|
+
result = assembly_image.create_jp2(output: jp2_output_file)
|
|
65
47
|
expect(assembly_image.tmp_path).not_to be_nil
|
|
66
48
|
expect(result).to be_a_kind_of described_class
|
|
67
|
-
expect(result.path).to eq
|
|
68
|
-
expect(
|
|
49
|
+
expect(result.path).to eq jp2_output_file
|
|
50
|
+
expect(jp2_output_file).to be_a_jp2
|
|
69
51
|
expect(result.exif.colorspace).to eq 'sRGB'
|
|
70
|
-
|
|
71
|
-
expect(
|
|
72
|
-
expect(jp2.width).to eq 37_838
|
|
52
|
+
expect(result.height).to eq 37_838
|
|
53
|
+
expect(result.width).to eq 37_838
|
|
73
54
|
end
|
|
74
55
|
end
|
|
75
56
|
|
|
76
57
|
context 'when given an LZW compressed RGB tif with more than 4GB of image data', skip: 'This test will create a 4GB temporary image, so skipping by default.' do
|
|
58
|
+
let(:input_path) { File.join(TEST_INPUT_DIR, 'lzw.tif') }
|
|
59
|
+
|
|
77
60
|
before do
|
|
78
|
-
generate_test_image(
|
|
61
|
+
generate_test_image(input_path, compress: 'lzw', width: '37838', height: '37838')
|
|
79
62
|
end
|
|
80
63
|
|
|
81
64
|
it 'creates the jp2 with a temp file' do
|
|
82
|
-
expect(File).to exist
|
|
83
|
-
expect(File).not_to exist
|
|
84
|
-
|
|
65
|
+
expect(File).to exist input_path
|
|
66
|
+
expect(File).not_to exist jp2_output_file
|
|
67
|
+
expect(assembly_image.exif.samplesperpixel).to be 3
|
|
68
|
+
expect(assembly_image.exif.bitspersample).to eql '8 8 8'
|
|
69
|
+
expect(assembly_image).to be_a_valid_image
|
|
70
|
+
expect(assembly_image).to be_jp2abl
|
|
71
|
+
result = assembly_image.create_jp2(output: jp2_output_file)
|
|
85
72
|
expect(assembly_image.tmp_path).not_to be_nil
|
|
86
73
|
expect(result).to be_a_kind_of described_class
|
|
87
|
-
expect(result.path).to eq
|
|
88
|
-
expect(
|
|
74
|
+
expect(result.path).to eq jp2_output_file
|
|
75
|
+
expect(jp2_output_file).to be_a_jp2
|
|
89
76
|
expect(result.exif.colorspace).to eq 'sRGB'
|
|
90
|
-
|
|
91
|
-
expect(
|
|
92
|
-
expect(jp2.width).to eq 37_838
|
|
77
|
+
expect(result.height).to eq 37_838
|
|
78
|
+
expect(result.width).to eq 37_838
|
|
93
79
|
end
|
|
94
80
|
end
|
|
95
81
|
|
|
96
82
|
context 'when given a bitonal tif' do
|
|
83
|
+
let(:input_path) { File.join(TEST_INPUT_DIR, 'bitonal.tif') }
|
|
84
|
+
|
|
97
85
|
before do
|
|
98
|
-
|
|
99
|
-
generate_test_image(TEST_TIF_INPUT_FILE, image_type: 'Bilevel', compress: 'group4')
|
|
86
|
+
generate_test_image(input_path, color: 'bin', bands: 1, depth: 1)
|
|
100
87
|
end
|
|
101
88
|
|
|
102
|
-
it 'creates
|
|
103
|
-
expect(File).to exist
|
|
104
|
-
expect(File).not_to exist
|
|
105
|
-
|
|
106
|
-
expect(
|
|
89
|
+
it 'creates valid jp2' do
|
|
90
|
+
expect(File).to exist input_path
|
|
91
|
+
expect(File).not_to exist jp2_output_file
|
|
92
|
+
expect(assembly_image.exif.samplesperpixel).to be 1
|
|
93
|
+
expect(assembly_image.exif.bitspersample).to be 1
|
|
94
|
+
expect(assembly_image).not_to have_color_profile
|
|
95
|
+
result = assembly_image.create_jp2(output: jp2_output_file)
|
|
96
|
+
expect(result).to be_a_kind_of described_class
|
|
97
|
+
expect(result.path).to eq jp2_output_file
|
|
98
|
+
expect(jp2_output_file).to be_a_jp2
|
|
107
99
|
expect(result.exif.colorspace).to eq 'Grayscale'
|
|
108
100
|
end
|
|
109
101
|
end
|
|
110
102
|
|
|
111
103
|
context 'when given a color tif but bitonal image data (1 channels and 1 bits per pixel)' do
|
|
104
|
+
let(:input_path) { File.join(TEST_INPUT_DIR, 'color.tif') }
|
|
105
|
+
|
|
112
106
|
before do
|
|
113
|
-
generate_test_image(
|
|
107
|
+
generate_test_image(input_path, color: 'bin', bands: 3)
|
|
114
108
|
end
|
|
115
109
|
|
|
116
110
|
it 'creates color jp2' do
|
|
117
|
-
expect(File).to exist
|
|
118
|
-
expect(File).not_to exist
|
|
111
|
+
expect(File).to exist input_path
|
|
112
|
+
expect(File).not_to exist jp2_output_file
|
|
119
113
|
expect(assembly_image).not_to have_color_profile
|
|
120
|
-
|
|
121
|
-
expect(
|
|
114
|
+
expect(assembly_image.exif.samplesperpixel).to be 3
|
|
115
|
+
expect(assembly_image.exif.bitspersample).to eql '8 8 8'
|
|
116
|
+
expect(assembly_image).to be_a_valid_image
|
|
117
|
+
expect(assembly_image).to be_jp2able
|
|
118
|
+
result = assembly_image.create_jp2(output: jp2_output_file)
|
|
119
|
+
expect(result).to be_a_kind_of described_class
|
|
120
|
+
expect(result.path).to eq jp2_output_file
|
|
121
|
+
expect(jp2_output_file).to be_a_jp2
|
|
122
122
|
expect(result.exif.colorspace).to eq 'sRGB'
|
|
123
123
|
end
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
context 'when given a graycale tif but with bitonal image data (1 channel and 1 bits per pixel)' do
|
|
127
|
+
let(:input_path) { File.join(TEST_INPUT_DIR, 'gray.tif') }
|
|
128
|
+
|
|
127
129
|
before do
|
|
128
|
-
generate_test_image(
|
|
130
|
+
generate_test_image(input_path, color: 'grey', bands: 1)
|
|
129
131
|
end
|
|
130
132
|
|
|
131
133
|
it 'creates grayscale jp2' do
|
|
132
|
-
expect(File).to exist
|
|
133
|
-
expect(File).not_to exist
|
|
134
|
+
expect(File).to exist input_path
|
|
135
|
+
expect(File).not_to exist jp2_output_file
|
|
134
136
|
expect(assembly_image).not_to have_color_profile
|
|
135
|
-
|
|
136
|
-
expect(
|
|
137
|
+
expect(assembly_image.exif.samplesperpixel).to be 1
|
|
138
|
+
expect(assembly_image.exif.bitspersample).to be 8
|
|
139
|
+
expect(assembly_image).to be_a_valid_image
|
|
140
|
+
expect(assembly_image).to be_jp2able
|
|
141
|
+
result = assembly_image.create_jp2(output: jp2_output_file)
|
|
142
|
+
expect(jp2_output_file).to be_a_jp2
|
|
143
|
+
expect(result).to be_a_kind_of described_class
|
|
144
|
+
expect(result.path).to eq jp2_output_file
|
|
137
145
|
expect(result.exif.colorspace).to eq 'Grayscale'
|
|
138
146
|
end
|
|
139
147
|
end
|
|
140
148
|
|
|
141
149
|
context 'when given a color tif but with greyscale image data (1 channel and 8 bits per pixel)' do
|
|
150
|
+
let(:input_path) { File.join(TEST_INPUT_DIR, 'color_gray.tif') }
|
|
151
|
+
|
|
142
152
|
before do
|
|
143
|
-
generate_test_image(
|
|
153
|
+
generate_test_image(input_path, color: 'grey')
|
|
144
154
|
end
|
|
145
155
|
|
|
146
156
|
it 'creates color jp2' do
|
|
147
|
-
expect(File).to exist
|
|
148
|
-
expect(File).not_to exist
|
|
157
|
+
expect(File).to exist input_path
|
|
158
|
+
expect(File).not_to exist jp2_output_file
|
|
159
|
+
expect(assembly_image.exif.samplesperpixel).to be 3
|
|
160
|
+
expect(assembly_image.exif.bitspersample).to eql '8 8 8'
|
|
161
|
+
expect(assembly_image).to be_a_valid_image
|
|
162
|
+
expect(assembly_image).to be_jp2able
|
|
149
163
|
expect(assembly_image).not_to have_color_profile
|
|
150
|
-
result = assembly_image.create_jp2(output:
|
|
151
|
-
expect(
|
|
164
|
+
result = assembly_image.create_jp2(output: jp2_output_file)
|
|
165
|
+
expect(result).to be_a_kind_of described_class
|
|
166
|
+
expect(result.path).to eq jp2_output_file
|
|
167
|
+
expect(jp2_output_file).to be_a_jp2
|
|
152
168
|
expect(result.exif.colorspace).to eq 'sRGB'
|
|
153
169
|
end
|
|
154
170
|
end
|
|
155
171
|
|
|
172
|
+
context 'when given a cmyk tif' do
|
|
173
|
+
let(:input_path) { File.join(TEST_INPUT_DIR, 'cmky.tif') }
|
|
174
|
+
|
|
175
|
+
before do
|
|
176
|
+
generate_test_image(input_path, color: 'cmyk', cg_type: 'cmyk', profile: 'cmyk', bands: 4)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it 'creates an srgb jp2', skip: 'Need to verify the color space is correct in jp2' do
|
|
180
|
+
expect(File).to exist input_path
|
|
181
|
+
expect(File).not_to exist jp2_output_file
|
|
182
|
+
expect(assembly_image.exif.samplesperpixel).to be 4
|
|
183
|
+
expect(assembly_image.exif.bitspersample).to eql '8 8 8 8'
|
|
184
|
+
expect(assembly_image).to be_a_valid_image
|
|
185
|
+
expect(assembly_image).to be_jp2able
|
|
186
|
+
expect(assembly_image).to have_color_profile
|
|
187
|
+
result = assembly_image.create_jp2(output: jp2_output_file)
|
|
188
|
+
expect(result).to be_a_kind_of described_class
|
|
189
|
+
expect(result.path).to eq jp2_output_file
|
|
190
|
+
expect(jp2_output_file).to be_a_jp2
|
|
191
|
+
# note, we verify the CMYK has been converted to an SRGB JP2 correctly by using ruby-vips instead of exif, since exif does not correctly
|
|
192
|
+
# identify the color space...note: this line current does not work in circleci, potentially due to libvips version differences
|
|
193
|
+
expect(Vips::Image.new_from_file(jp2_output_file).get_value('interpretation')).to eq :srgb
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
156
197
|
context 'when the source image has no profile' do
|
|
198
|
+
let(:input_path) { File.join(TEST_INPUT_DIR, 'no_profile.tif') }
|
|
199
|
+
|
|
157
200
|
before do
|
|
158
|
-
generate_test_image(
|
|
201
|
+
generate_test_image(input_path)
|
|
159
202
|
end
|
|
160
203
|
|
|
161
204
|
it 'creates a jp2' do
|
|
162
|
-
expect(File).to exist
|
|
163
|
-
expect(File).not_to exist
|
|
205
|
+
expect(File).to exist input_path
|
|
206
|
+
expect(File).not_to exist jp2_output_file
|
|
207
|
+
expect(assembly_image.exif.samplesperpixel).to be 3
|
|
208
|
+
expect(assembly_image.exif.bitspersample).to eql '8 8 8'
|
|
164
209
|
expect(assembly_image).not_to have_color_profile
|
|
165
210
|
expect(assembly_image).to be_a_valid_image
|
|
166
211
|
expect(assembly_image).to be_jp2able
|
|
167
|
-
assembly_image.create_jp2(output:
|
|
168
|
-
expect(
|
|
212
|
+
assembly_image.create_jp2(output: jp2_output_file)
|
|
213
|
+
expect(jp2_output_file).to be_a_jp2
|
|
169
214
|
end
|
|
170
215
|
end
|
|
171
216
|
|
|
172
217
|
context "when the output file exists and you don't allow overwriting" do
|
|
173
218
|
before do
|
|
174
|
-
generate_test_image(
|
|
175
|
-
|
|
219
|
+
generate_test_image(input_path)
|
|
220
|
+
FileUtils.touch(jp2_output_file) # just need a file with this name, don't care what
|
|
176
221
|
end
|
|
177
222
|
|
|
178
223
|
it 'does not run' do
|
|
179
|
-
expect(File).to exist
|
|
180
|
-
expect(File).to exist
|
|
181
|
-
expect { assembly_image.create_jp2(output:
|
|
224
|
+
expect(File).to exist input_path
|
|
225
|
+
expect(File).to exist jp2_output_file
|
|
226
|
+
expect { assembly_image.create_jp2(output: jp2_output_file) }.to raise_error(SecurityError)
|
|
182
227
|
end
|
|
183
228
|
end
|
|
184
229
|
|
|
185
230
|
context 'when given a test tiff' do
|
|
186
231
|
before do
|
|
187
|
-
generate_test_image(
|
|
232
|
+
generate_test_image(input_path)
|
|
188
233
|
end
|
|
189
234
|
|
|
190
235
|
it 'gets the correct image height and width' do
|
|
191
|
-
expect(assembly_image.height).to eq
|
|
192
|
-
expect(assembly_image.width).to eq
|
|
236
|
+
expect(assembly_image.height).to eq 36
|
|
237
|
+
expect(assembly_image.width).to eq 43
|
|
193
238
|
end
|
|
194
239
|
end
|
|
195
240
|
|
|
196
241
|
context 'when the input file is a jp2' do
|
|
197
242
|
before do
|
|
198
|
-
generate_test_image(
|
|
243
|
+
generate_test_image(input_path)
|
|
199
244
|
end
|
|
200
245
|
|
|
201
246
|
it 'does not run' do
|
|
202
|
-
expect(File).to exist
|
|
203
|
-
expect(File).not_to exist
|
|
247
|
+
expect(File).to exist input_path
|
|
248
|
+
expect(File).not_to exist jp2_output_file
|
|
204
249
|
expect(assembly_image).not_to have_color_profile
|
|
205
250
|
expect(assembly_image).to be_a_valid_image
|
|
206
251
|
expect(assembly_image).to be_jp2able
|
|
207
|
-
assembly_image.create_jp2(output:
|
|
208
|
-
expect(
|
|
209
|
-
jp2_file = described_class.new(
|
|
252
|
+
assembly_image.create_jp2(output: jp2_output_file)
|
|
253
|
+
expect(jp2_output_file).to be_a_jp2
|
|
254
|
+
jp2_file = described_class.new(jp2_output_file)
|
|
210
255
|
expect(jp2_file).to be_valid_image
|
|
211
256
|
expect(jp2_file).not_to be_jp2able
|
|
212
|
-
expect { jp2_file.create_jp2 }.to raise_error
|
|
257
|
+
expect { jp2_file.create_jp2 }.to raise_error(RuntimeError)
|
|
213
258
|
end
|
|
214
259
|
end
|
|
215
260
|
|
|
216
261
|
context 'when you specify a bogus output profile' do
|
|
217
262
|
before do
|
|
218
|
-
generate_test_image(
|
|
263
|
+
generate_test_image(input_path)
|
|
219
264
|
end
|
|
220
265
|
|
|
221
266
|
it 'runs, because this is not currently an option' do
|
|
222
|
-
expect(File).to exist
|
|
267
|
+
expect(File).to exist input_path
|
|
223
268
|
result = assembly_image.create_jp2(output_profile: 'bogusness')
|
|
224
269
|
expect(result).to be_a_kind_of described_class
|
|
225
270
|
expect(result.path).to eq TEST_JP2_INPUT_FILE
|
|
@@ -239,17 +284,17 @@ RSpec.describe Assembly::Image do
|
|
|
239
284
|
bogus_folder = '/crapsticks'
|
|
240
285
|
expect(File).to exist TEST_JPEG_INPUT_FILE
|
|
241
286
|
expect(File).not_to exist bogus_folder
|
|
242
|
-
expect { assembly_image.create_jp2(tmp_folder: bogus_folder) }.to raise_error
|
|
287
|
+
expect { assembly_image.create_jp2(tmp_folder: bogus_folder) }.to raise_error(RuntimeError)
|
|
243
288
|
end
|
|
244
289
|
end
|
|
245
290
|
|
|
246
291
|
context 'when no output file is specified' do
|
|
247
292
|
before do
|
|
248
|
-
generate_test_image(
|
|
293
|
+
generate_test_image(input_path)
|
|
249
294
|
end
|
|
250
295
|
|
|
251
296
|
it 'creates a jp2 of the same filename and in the same location as the input and cleans up the tmp file' do
|
|
252
|
-
expect(File).to exist
|
|
297
|
+
expect(File).to exist input_path
|
|
253
298
|
expect(File.exist?(TEST_JP2_INPUT_FILE)).to be false
|
|
254
299
|
result = assembly_image.create_jp2
|
|
255
300
|
expect(result).to be_a_kind_of described_class
|
|
@@ -261,17 +306,17 @@ RSpec.describe Assembly::Image do
|
|
|
261
306
|
|
|
262
307
|
context 'when the output file exists and you allow overwriting' do
|
|
263
308
|
before do
|
|
264
|
-
generate_test_image(
|
|
265
|
-
|
|
309
|
+
generate_test_image(input_path)
|
|
310
|
+
FileUtils.touch(jp2_output_file) # just need a file with this name, don't care what
|
|
266
311
|
end
|
|
267
312
|
|
|
268
313
|
it 'recreates jp2' do
|
|
269
|
-
expect(File).to exist
|
|
270
|
-
expect(File).to exist
|
|
271
|
-
result = assembly_image.create_jp2(output:
|
|
314
|
+
expect(File).to exist input_path
|
|
315
|
+
expect(File).to exist jp2_output_file
|
|
316
|
+
result = assembly_image.create_jp2(output: jp2_output_file, overwrite: true)
|
|
272
317
|
expect(result).to be_a_kind_of described_class
|
|
273
|
-
expect(result.path).to eq
|
|
274
|
-
expect(
|
|
318
|
+
expect(result.path).to eq jp2_output_file
|
|
319
|
+
expect(jp2_output_file).to be_a_jp2
|
|
275
320
|
expect(result.exif.colorspace).to eq 'sRGB'
|
|
276
321
|
end
|
|
277
322
|
end
|
data/spec/images_spec.rb
CHANGED
|
@@ -3,45 +3,45 @@
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
|
|
5
5
|
RSpec.describe Assembly::Images do
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
before { cleanup }
|
|
7
|
+
|
|
8
|
+
describe '#batch_generate_jp2' do
|
|
9
|
+
it 'does not run if no input folder is passed in' do
|
|
10
|
+
expect{ described_class.batch_generate_jp2('') }.to raise_error(RuntimeError)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'does not run if a non-existent input folder is passed in' do
|
|
14
|
+
expect{ described_class.batch_generate_jp2('/junk/path') }.to raise_error(RuntimeError)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'runs and batch produces jp2s from input tiffs' do
|
|
18
|
+
['test1', 'test2', 'test3'].each { |image| generate_test_image(File.join(TEST_INPUT_DIR, "#{image}.tif"), profile: 'AdobeRGB1998') }
|
|
19
|
+
described_class.batch_generate_jp2(TEST_INPUT_DIR, output: TEST_OUTPUT_DIR)
|
|
20
|
+
expect(File.directory?(TEST_OUTPUT_DIR)).to be true
|
|
21
|
+
['test1', 'test2', 'test3'].each { |image| expect(File.join(TEST_OUTPUT_DIR, "#{image}.jp2")).to be_a_jp2 }
|
|
22
|
+
end
|
|
10
23
|
end
|
|
11
24
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
it 'runs and batch add color profile descriptions input tiffs, forcing over existing color profile descriptions' do
|
|
35
|
-
['test1', 'test2', 'test3'].each { |image| generate_test_image(File.join(TEST_INPUT_DIR, "#{image}.tif")) }
|
|
36
|
-
['test1', 'test2', 'test3'].each { |image| expect(Assembly::Image.new(File.join(TEST_INPUT_DIR, "#{image}.tif")).exif.profiledescription).to eq 'sRGB IEC61966-2.1' }
|
|
37
|
-
described_class.batch_add_exif_profile_descr(TEST_INPUT_DIR, 'Adobe RGB 1998', force: true) # force overwrite
|
|
38
|
-
['test1', 'test2', 'test3'].each { |image| expect(Assembly::Image.new(File.join(TEST_INPUT_DIR, "#{image}.tif")).exif.profiledescription).to eq 'Adobe RGB (1998)' }
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
it 'runs and batch add color profile descriptions input tiffs, not overwriting existing color profile descriptions' do
|
|
42
|
-
['test1', 'test2', 'test3'].each { |image| generate_test_image(File.join(TEST_INPUT_DIR, "#{image}.tif")) }
|
|
43
|
-
['test1', 'test2', 'test3'].each { |image| expect(Assembly::Image.new(File.join(TEST_INPUT_DIR, "#{image}.tif")).exif.profiledescription).to eq 'sRGB IEC61966-2.1' }
|
|
44
|
-
described_class.batch_add_exif_profile_descr(TEST_INPUT_DIR, 'Adobe RGB 1998') # do not force overwrite
|
|
45
|
-
['test1', 'test2', 'test3'].each { |image| expect(Assembly::Image.new(File.join(TEST_INPUT_DIR, "#{image}.tif")).exif.profiledescription).to eq 'sRGB IEC61966-2.1' }
|
|
25
|
+
describe '#batch_add_exif_profile_descr' do
|
|
26
|
+
it 'runs and batch adds color profile descriptions to input tiffs that had no color profile descriptions' do
|
|
27
|
+
['test1', 'test2', 'test3'].each { |image| generate_test_image(File.join(TEST_INPUT_DIR, "#{image}.tif")) }
|
|
28
|
+
['test1', 'test2', 'test3'].each { |image| expect(Assembly::Image.new(File.join(TEST_INPUT_DIR, "#{image}.tif")).exif.profiledescription).to be_nil }
|
|
29
|
+
described_class.batch_add_exif_profile_descr(TEST_INPUT_DIR, 'Adobe RGB 1998')
|
|
30
|
+
['test1', 'test2', 'test3'].each { |image| expect(Assembly::Image.new(File.join(TEST_INPUT_DIR, "#{image}.tif")).exif.profiledescription).to eq 'Adobe RGB (1998)' }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'runs and batch adds color profile descriptions to input tiffs, forcing over existing color profile descriptions' do
|
|
34
|
+
['test1', 'test2', 'test3'].each { |image| generate_test_image(File.join(TEST_INPUT_DIR, "#{image}.tif"), profile: 'sRGBIEC6196621') }
|
|
35
|
+
['test1', 'test2', 'test3'].each { |image| expect(Assembly::Image.new(File.join(TEST_INPUT_DIR, "#{image}.tif")).exif.profiledescription).to eq 'sRGB IEC61966-2.1' }
|
|
36
|
+
described_class.batch_add_exif_profile_descr(TEST_INPUT_DIR, 'Adobe RGB 1998', force: true) # force overwrite
|
|
37
|
+
['test1', 'test2', 'test3'].each { |image| expect(Assembly::Image.new(File.join(TEST_INPUT_DIR, "#{image}.tif")).exif.profiledescription).to eq 'Adobe RGB (1998)' }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'runs and batch adds color profile descriptions to input tiffs, not overwriting existing color profile descriptions' do
|
|
41
|
+
['test1', 'test2', 'test3'].each { |image| generate_test_image(File.join(TEST_INPUT_DIR, "#{image}.tif"), profile: 'sRGBIEC6196621') }
|
|
42
|
+
['test1', 'test2', 'test3'].each { |image| expect(Assembly::Image.new(File.join(TEST_INPUT_DIR, "#{image}.tif")).exif.profiledescription).to eq 'sRGB IEC61966-2.1' }
|
|
43
|
+
described_class.batch_add_exif_profile_descr(TEST_INPUT_DIR, 'Adobe RGB 1998') # do not force overwrite
|
|
44
|
+
['test1', 'test2', 'test3'].each { |image| expect(Assembly::Image.new(File.join(TEST_INPUT_DIR, "#{image}.tif")).exif.profiledescription).to eq 'sRGB IEC61966-2.1' }
|
|
45
|
+
end
|
|
46
46
|
end
|
|
47
47
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -7,14 +7,17 @@ end
|
|
|
7
7
|
|
|
8
8
|
bootfile = File.expand_path(File.dirname(__FILE__) + '/../config/boot')
|
|
9
9
|
require bootfile
|
|
10
|
+
require 'ruby-vips'
|
|
11
|
+
require 'pry-byebug'
|
|
10
12
|
|
|
11
13
|
TEST_INPUT_DIR = File.join(Assembly::PATH_TO_IMAGE_GEM, 'spec', 'test_data', 'input')
|
|
12
14
|
TEST_OUTPUT_DIR = File.join(Assembly::PATH_TO_IMAGE_GEM, 'spec', 'test_data', 'output')
|
|
13
15
|
TEST_TIF_INPUT_FILE = File.join(TEST_INPUT_DIR, 'test.tif')
|
|
14
|
-
TEST_DPG_TIF_INPUT_FILE = File.join(TEST_INPUT_DIR, 'oo000oo0001_00_01.tif')
|
|
15
16
|
TEST_JPEG_INPUT_FILE = File.join(TEST_INPUT_DIR, 'test.jpg')
|
|
16
17
|
TEST_JP2_INPUT_FILE = File.join(TEST_INPUT_DIR, 'test.jp2')
|
|
17
18
|
TEST_JP2_OUTPUT_FILE = File.join(TEST_OUTPUT_DIR, 'test.jp2')
|
|
19
|
+
TEST_PROFILE_DIR = File.join(Assembly::PATH_TO_IMAGE_GEM, 'profiles')
|
|
20
|
+
TEST_DATA_DIR = File.join(Assembly::PATH_TO_IMAGE_GEM, 'spec', 'test_data')
|
|
18
21
|
TEST_DRUID = 'nx288wh8889'
|
|
19
22
|
|
|
20
23
|
RSpec.configure do |config|
|
|
@@ -101,31 +104,131 @@ RSpec.configure do |config|
|
|
|
101
104
|
Kernel.srand config.seed
|
|
102
105
|
end
|
|
103
106
|
|
|
107
|
+
# rubocop:disable Metrics/MethodLength
|
|
108
|
+
# Color values for 30-patch ColorGauge color target.
|
|
109
|
+
def color_gauge_values(type = 'adobeRGB')
|
|
110
|
+
# rubocop:disable Layout/SpaceInsideArrayLiteralBrackets
|
|
111
|
+
# rubocop:disable Layout/ExtraSpacing
|
|
112
|
+
adobe_rgb = [
|
|
113
|
+
[109, 83, 71], [187, 146, 129], [101, 120, 151], [ 97, 108, 68], [130, 128, 172],
|
|
114
|
+
[130, 187, 171], [ 64, 134, 165], [241, 242, 237], [231, 232, 229], [216, 217, 215],
|
|
115
|
+
[203, 204, 203], [202, 125, 55], [172, 87, 147], [174, 176, 175], [148, 150, 149],
|
|
116
|
+
[116, 119, 118], [ 91, 91, 92], [ 78, 92, 165], [227, 198, 55], [ 68, 70, 69],
|
|
117
|
+
[ 48, 48, 48], [ 32, 32, 32], [ 23, 23, 23], [175, 85, 97], [157, 60, 61],
|
|
118
|
+
[100, 148, 80], [ 53, 67, 141], [213, 160, 56], [167, 187, 77], [ 86, 61, 100]
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
srgb = [
|
|
122
|
+
[118, 82, 69], [202, 147, 129], [ 92, 121, 154], [ 92, 109, 64], [132, 129, 175],
|
|
123
|
+
[ 96, 188, 172], [ 0, 135, 168], [241, 242, 237], [231, 232, 229], [217, 218, 216],
|
|
124
|
+
[204, 205, 204], [225, 126, 46], [196, 86, 150], [175, 178, 177], [148, 151, 150],
|
|
125
|
+
[116, 120, 119], [ 91, 91, 92], [ 70, 92, 169], [238, 199, 27], [ 65, 68, 67],
|
|
126
|
+
[ 44, 44, 44], [ 26, 26, 26], [ 16, 16, 16], [200, 84, 97], [181, 57, 58],
|
|
127
|
+
[ 68, 149, 74], [ 42, 65, 145], [231, 161, 41], [160, 188, 65], [ 94, 58, 101]
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
cmyk = [
|
|
131
|
+
[120, 154, 169, 84], [ 69, 110, 120, 5], [169, 125, 64, 8], [154, 105, 207, 64],
|
|
132
|
+
[138, 125, 31, 0], [128, 26, 95, 0], [195, 95, 61, 3], [ 10, 5, 13, 0],
|
|
133
|
+
[ 20, 13, 18, 0], [ 36, 26, 31, 0], [ 51, 38, 41, 0], [ 46, 143, 236, 8],
|
|
134
|
+
[ 90, 202, 31, 0], [ 84, 64, 69, 0], [115, 90, 95, 0], [143, 115, 120, 28],
|
|
135
|
+
[161, 141, 136, 69], [205, 182, 8, 0], [ 33, 46, 238, 0], [172, 151, 151, 110],
|
|
136
|
+
[179, 164, 161, 156], [184, 169, 166, 189], [187, 172, 166, 205], [ 69, 197, 131, 20],
|
|
137
|
+
[ 69, 220, 189, 51], [161, 59, 223, 15], [241, 223, 28, 5], [ 44, 95, 238, 3],
|
|
138
|
+
[100, 31, 228, 0], [184, 210, 90, 56]
|
|
139
|
+
]
|
|
140
|
+
# rubocop:enable Layout/SpaceInsideArrayLiteralBrackets
|
|
141
|
+
# rubocop:enable Layout/ExtraSpacing
|
|
142
|
+
case type
|
|
143
|
+
when 'adobe_rgb'
|
|
144
|
+
adobe_rgb
|
|
145
|
+
when 'srgb'
|
|
146
|
+
srgb
|
|
147
|
+
when 'cmyk'
|
|
148
|
+
cmyk
|
|
149
|
+
else
|
|
150
|
+
raise 'Unknown color_gauge_values type.'
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
# rubocop:enable Metrics/MethodLength
|
|
154
|
+
|
|
104
155
|
# generate a sample image file with a specified profile
|
|
105
156
|
# rubocop:disable Metrics/AbcSize
|
|
106
157
|
# rubocop:disable Metrics/CyclomaticComplexity
|
|
107
158
|
# rubocop:disable Metrics/MethodLength
|
|
108
159
|
# rubocop:disable Metrics/PerceivedComplexity
|
|
109
160
|
def generate_test_image(file, params = {})
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
161
|
+
# Set default size for sample test image.
|
|
162
|
+
line_size = 1
|
|
163
|
+
box_size = 6
|
|
164
|
+
width = (box_size * 6) + (line_size * 7)
|
|
165
|
+
height = (box_size * 5) + (line_size * 6)
|
|
166
|
+
|
|
167
|
+
# Set parameters for image creation options.
|
|
168
|
+
image_type = params[:image_type] || File.extname(file)
|
|
169
|
+
bands = params[:bands] || 3
|
|
170
|
+
color = params[:color] || 'rgb'
|
|
171
|
+
depth = params[:depth] || 8
|
|
172
|
+
cg_type = params[:cg_type] || 'adobe_rgb'
|
|
173
|
+
compression = params[:compression]
|
|
174
|
+
profile = params[:profile]
|
|
175
|
+
|
|
176
|
+
temp_array = color_gauge_values(cg_type)
|
|
177
|
+
temp_image = Vips::Image.black(width, height, bands: temp_array.first.size)
|
|
178
|
+
(0..4).each do |i|
|
|
179
|
+
b = (box_size * i) + (line_size * (i + 1))
|
|
180
|
+
# d = b + box_size - line_size
|
|
181
|
+
(0...6).each do |j|
|
|
182
|
+
a = (box_size * j) + (line_size * (j + 1))
|
|
183
|
+
# c = a + box_size - line_size
|
|
184
|
+
colors = temp_array.shift
|
|
185
|
+
temp_image = temp_image.draw_rect(colors, a, b, box_size, box_size, fill: true)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
temp_image = color.eql?('cmyk') ? temp_image.copy(interpretation: :cmyk) : temp_image.copy(interpretation: :srgb)
|
|
190
|
+
|
|
191
|
+
temp_image = if color.eql?('grey') && bands == 3
|
|
192
|
+
mean = temp_image.bandmean
|
|
193
|
+
Vips::Image.bandjoin([mean, mean, mean])
|
|
194
|
+
elsif color.eql?('grey') && bands == 1
|
|
195
|
+
temp_image.bandmean
|
|
196
|
+
elsif color.eql?('bin') && bands == 3
|
|
197
|
+
mean = temp_image.bandmean < 128
|
|
198
|
+
Vips::Image.bandjoin([mean, mean, mean])
|
|
199
|
+
elsif color.eql?('bin') && bands == 1
|
|
200
|
+
temp_image.bandmean < 128
|
|
201
|
+
else
|
|
202
|
+
temp_image
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
options = {}
|
|
206
|
+
unless profile.nil?
|
|
207
|
+
profile_file = File.join(TEST_PROFILE_DIR, profile + '.icc')
|
|
208
|
+
options.merge!(profile: profile_file)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
case image_type
|
|
212
|
+
when '.tiff', '.tif'
|
|
213
|
+
options.merge!(compression: compression) unless compression.nil?
|
|
214
|
+
options.merge!(squash: true) if depth.eql?(1)
|
|
215
|
+
temp_image.tiffsave(file, **options)
|
|
216
|
+
when '.jpeg', '.jpg'
|
|
217
|
+
temp_image.jpegsave(file, **options)
|
|
218
|
+
else
|
|
219
|
+
raise "unknown type: #{image_type}"
|
|
220
|
+
end
|
|
123
221
|
end
|
|
124
222
|
# rubocop:enable Metrics/AbcSize
|
|
125
223
|
# rubocop:enable Metrics/CyclomaticComplexity
|
|
126
224
|
# rubocop:enable Metrics/MethodLength
|
|
127
225
|
# rubocop:enable Metrics/PerceivedComplexity
|
|
128
226
|
|
|
227
|
+
def cleanup
|
|
228
|
+
remove_files(TEST_INPUT_DIR)
|
|
229
|
+
remove_files(TEST_OUTPUT_DIR)
|
|
230
|
+
end
|
|
231
|
+
|
|
129
232
|
def remove_files(dir)
|
|
130
233
|
Dir.foreach(dir) do |f|
|
|
131
234
|
fn = File.join(dir, f)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: assembly-image
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter Mangiafico
|
|
@@ -11,7 +11,7 @@ authors:
|
|
|
11
11
|
autorequire:
|
|
12
12
|
bindir: exe
|
|
13
13
|
cert_chain: []
|
|
14
|
-
date: 2022-06-
|
|
14
|
+
date: 2022-06-27 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
17
17
|
name: assembly-objectfile
|
|
@@ -47,6 +47,34 @@ dependencies:
|
|
|
47
47
|
- - "<"
|
|
48
48
|
- !ruby/object:Gem::Version
|
|
49
49
|
version: '3'
|
|
50
|
+
- !ruby/object:Gem::Dependency
|
|
51
|
+
name: ruby-vips
|
|
52
|
+
requirement: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '2.0'
|
|
57
|
+
type: :runtime
|
|
58
|
+
prerelease: false
|
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: '2.0'
|
|
64
|
+
- !ruby/object:Gem::Dependency
|
|
65
|
+
name: pry-byebug
|
|
66
|
+
requirement: !ruby/object:Gem::Requirement
|
|
67
|
+
requirements:
|
|
68
|
+
- - ">="
|
|
69
|
+
- !ruby/object:Gem::Version
|
|
70
|
+
version: '0'
|
|
71
|
+
type: :development
|
|
72
|
+
prerelease: false
|
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
50
78
|
- !ruby/object:Gem::Dependency
|
|
51
79
|
name: rake
|
|
52
80
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -152,7 +180,6 @@ files:
|
|
|
152
180
|
- Rakefile
|
|
153
181
|
- assembly-image.gemspec
|
|
154
182
|
- bin/console
|
|
155
|
-
- bin/run_all_tests
|
|
156
183
|
- config/boot.rb
|
|
157
184
|
- lib/assembly-image.rb
|
|
158
185
|
- lib/assembly-image/image.rb
|
|
@@ -160,11 +187,20 @@ files:
|
|
|
160
187
|
- lib/assembly-image/jp2_creator.rb
|
|
161
188
|
- profiles/AdobeRGB1998.icc
|
|
162
189
|
- profiles/DotGain20.icc
|
|
190
|
+
- profiles/cmyk.icc
|
|
163
191
|
- profiles/sRGBIEC6196621.icc
|
|
164
192
|
- spec/assembly/image/jp2_creator_spec.rb
|
|
165
193
|
- spec/image_spec.rb
|
|
166
194
|
- spec/images_spec.rb
|
|
167
195
|
- spec/spec_helper.rb
|
|
196
|
+
- spec/test_data/color_cmyk_tagged.tif
|
|
197
|
+
- spec/test_data/color_cmyk_untagged.tif
|
|
198
|
+
- spec/test_data/color_rgb_adobergb1998_lzw.tif
|
|
199
|
+
- spec/test_data/color_rgb_srgb.jpg
|
|
200
|
+
- spec/test_data/color_rgb_srgb.tif
|
|
201
|
+
- spec/test_data/color_rgb_srgb_rot90cw.tif
|
|
202
|
+
- spec/test_data/color_rgb_untagged.tif
|
|
203
|
+
- spec/test_data/gray_gray_untagged.tif
|
|
168
204
|
- spec/test_data/input/.empty
|
|
169
205
|
- spec/test_data/output/.empty
|
|
170
206
|
homepage: ''
|
|
@@ -191,10 +227,4 @@ signing_key:
|
|
|
191
227
|
specification_version: 4
|
|
192
228
|
summary: Ruby immplementation of image services needed to prepare objects to be accessioned
|
|
193
229
|
in SULAIR digital library
|
|
194
|
-
test_files:
|
|
195
|
-
- spec/assembly/image/jp2_creator_spec.rb
|
|
196
|
-
- spec/image_spec.rb
|
|
197
|
-
- spec/images_spec.rb
|
|
198
|
-
- spec/spec_helper.rb
|
|
199
|
-
- spec/test_data/input/.empty
|
|
200
|
-
- spec/test_data/output/.empty
|
|
230
|
+
test_files: []
|
data/bin/run_all_tests
DELETED