assembly-image 1.9.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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