rublurhash 0.0.1
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 +7 -0
- data/.document +3 -0
- data/.gitignore +8 -0
- data/.gitlab-ci.yml +30 -0
- data/.yardopts +4 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +127 -0
- data/Rakefile +19 -0
- data/Steepfile +34 -0
- data/ext/Cargo.lock +1387 -0
- data/ext/Cargo.toml +14 -0
- data/ext/src/lib.rs +243 -0
- data/rublurhash.gemspec +28 -0
- data/sig/rublurhash.rbs +14 -0
- data/sig/test/fiddle.rbs +8 -0
- data/sig/test/rubygems.rbs +10 -0
- data/sig/test/test-unit.rbs +18 -0
- data/sig/test/test.rbs +28 -0
- data/test/helper.rb +7 -0
- data/test/test_package.rb +27 -0
- data/test/test_rublurhash.rb +96 -0
- metadata +203 -0
data/ext/Cargo.toml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "rublurhash"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
edition = "2024"
|
|
5
|
+
authors = ["Kitait Makoto <KitaitiMakoto@gmail.com>"]
|
|
6
|
+
publish = false
|
|
7
|
+
|
|
8
|
+
[lib]
|
|
9
|
+
crate-type = ["cdylib"]
|
|
10
|
+
|
|
11
|
+
[dependencies]
|
|
12
|
+
magnus = ">= 0.8.2"
|
|
13
|
+
blurhash-core = { version = "0.1.0", features = ["simd", "gpu"] }
|
|
14
|
+
grey-knights = "=0.1.1"
|
data/ext/src/lib.rs
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
use grey_knights::memory_view::{
|
|
2
|
+
Builder, Entry, Flags, FlagsChainable, ValidatedMemoryView, register,
|
|
3
|
+
};
|
|
4
|
+
use magnus::{
|
|
5
|
+
Attr, Module, Object, RArray, RClass, RModule, RString, Ruby, TryConvert, Value,
|
|
6
|
+
encoding::EncodingCapable,
|
|
7
|
+
error::IntoError,
|
|
8
|
+
function,
|
|
9
|
+
scan_args::{get_kwargs, scan_args},
|
|
10
|
+
value::ReprValue,
|
|
11
|
+
};
|
|
12
|
+
use std::num::TryFromIntError;
|
|
13
|
+
|
|
14
|
+
enum Error {
|
|
15
|
+
Arg(String),
|
|
16
|
+
Runtime(String),
|
|
17
|
+
Magnus(magnus::Error),
|
|
18
|
+
Blurhash(blurhash_core::BlurhashError),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl magnus::error::IntoError for Error {
|
|
22
|
+
fn into_error(self, ruby: &Ruby) -> magnus::Error {
|
|
23
|
+
use Error::*;
|
|
24
|
+
|
|
25
|
+
match self {
|
|
26
|
+
Arg(msg) => magnus::Error::new(ruby.exception_arg_error(), msg),
|
|
27
|
+
Runtime(msg) => magnus::Error::new(ruby.exception_runtime_error(), msg),
|
|
28
|
+
Magnus(err) => err,
|
|
29
|
+
Blurhash(err) => magnus::Error::new(ruby.exception_runtime_error(), format!("{}", err)),
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
impl From<magnus::Error> for Error {
|
|
35
|
+
fn from(value: magnus::Error) -> Self {
|
|
36
|
+
Self::Magnus(value)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl From<blurhash_core::BlurhashError> for Error {
|
|
41
|
+
fn from(value: blurhash_core::BlurhashError) -> Self {
|
|
42
|
+
Self::Blurhash(value)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl From<TryFromIntError> for Error {
|
|
47
|
+
fn from(value: TryFromIntError) -> Self {
|
|
48
|
+
Self::Arg(format!("{}", value))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type Result<T> = std::result::Result<T, Error>;
|
|
53
|
+
|
|
54
|
+
fn encode(ruby: &Ruby, arguments: &[Value]) -> Result<String> {
|
|
55
|
+
let args = scan_args::<(Value,), (), (), (), _, ()>(arguments)?;
|
|
56
|
+
let kws = get_kwargs::<_, (u32, u32), (Option<Option<u32>>, Option<Option<u32>>), ()>(
|
|
57
|
+
args.keywords,
|
|
58
|
+
&["components_x", "components_y"],
|
|
59
|
+
&["width", "height"],
|
|
60
|
+
)?;
|
|
61
|
+
let (components_x, components_y) = kws.required;
|
|
62
|
+
let (width, height) = kws.optional;
|
|
63
|
+
let width = width.flatten();
|
|
64
|
+
let height = height.flatten();
|
|
65
|
+
match (width, height) {
|
|
66
|
+
(None, None) => {
|
|
67
|
+
let pxs = args.required.0;
|
|
68
|
+
let memview = ValidatedMemoryView::<u8>::new(
|
|
69
|
+
pxs,
|
|
70
|
+
Flags::format().multi_dimensional().row_major(),
|
|
71
|
+
)?;
|
|
72
|
+
// TODO: Support ndim == 2 && format == "CCC"
|
|
73
|
+
if memview.ndim() != 3_isize {
|
|
74
|
+
return Err(Error::Arg("ndim must be 3".into()));
|
|
75
|
+
}
|
|
76
|
+
let shape = memview
|
|
77
|
+
.shape()
|
|
78
|
+
.ok_or_else(|| Error::Arg("shape must not be null".into()))?;
|
|
79
|
+
if shape.len() != 3 {
|
|
80
|
+
return Err(Error::Arg("shape length must be 3".into()));
|
|
81
|
+
}
|
|
82
|
+
if shape[2] != 3 {
|
|
83
|
+
return Err(Error::Arg("shape[2] must be 3".into()));
|
|
84
|
+
}
|
|
85
|
+
if !memview.is_row_major_contiguous() {
|
|
86
|
+
return Err(Error::Arg("must be row-major contiguous".into()));
|
|
87
|
+
}
|
|
88
|
+
if memview.format().is_some() {
|
|
89
|
+
let item_desc = memview.item_desc()?;
|
|
90
|
+
if item_desc.len() != 1 {
|
|
91
|
+
return Err(Error::Arg(format!(
|
|
92
|
+
"item_desc length must be 1, but was {}",
|
|
93
|
+
item_desc.len()
|
|
94
|
+
)));
|
|
95
|
+
}
|
|
96
|
+
let component = item_desc
|
|
97
|
+
.into_iter()
|
|
98
|
+
.next()
|
|
99
|
+
.expect("item_desc length is exactly 1");
|
|
100
|
+
if component.format != 'C' || component.repeat != 1 {
|
|
101
|
+
return Err(Error::Arg(format!(
|
|
102
|
+
"format must be NULL or \"C\", but was {}",
|
|
103
|
+
component.format
|
|
104
|
+
)));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
blurhash_core::gpu::encode_gpu(
|
|
108
|
+
memview.data(),
|
|
109
|
+
u32::try_from(shape[1])?,
|
|
110
|
+
u32::try_from(shape[0])?,
|
|
111
|
+
components_x,
|
|
112
|
+
components_y,
|
|
113
|
+
)
|
|
114
|
+
.map_err(Error::Blurhash)
|
|
115
|
+
}
|
|
116
|
+
(Some(width), Some(height)) => {
|
|
117
|
+
let pixels = arguments[0];
|
|
118
|
+
if RArray::from_value(pixels).is_some() {
|
|
119
|
+
blurhash_core::gpu::encode_gpu(
|
|
120
|
+
Vec::<u8>::try_convert(pixels)?.as_slice(),
|
|
121
|
+
width,
|
|
122
|
+
height,
|
|
123
|
+
components_x,
|
|
124
|
+
components_y,
|
|
125
|
+
)
|
|
126
|
+
} else if let Some(pxs) = RString::from_value(pixels) {
|
|
127
|
+
if pxs.enc_get() != ruby.ascii8bit_encindex() {
|
|
128
|
+
return Err(Error::Arg("Not ASCII-8BIT".into()));
|
|
129
|
+
}
|
|
130
|
+
blurhash_core::gpu::encode_gpu(
|
|
131
|
+
unsafe { pxs.as_slice() },
|
|
132
|
+
width,
|
|
133
|
+
height,
|
|
134
|
+
components_x,
|
|
135
|
+
components_y,
|
|
136
|
+
)
|
|
137
|
+
} else {
|
|
138
|
+
return Err(Error::Arg(format!(
|
|
139
|
+
"First argument must be Array or String, but given: {}",
|
|
140
|
+
pixels.class()
|
|
141
|
+
)));
|
|
142
|
+
}
|
|
143
|
+
.map_err(Error::Blurhash)
|
|
144
|
+
}
|
|
145
|
+
_ => Err(Error::Arg(
|
|
146
|
+
"Both width and height are required if given".into(),
|
|
147
|
+
)),
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
struct ImageString();
|
|
152
|
+
|
|
153
|
+
impl ImageString {
|
|
154
|
+
fn init(ruby: &Ruby, rb_self: Value, width: u32, height: u32) -> Result<()> {
|
|
155
|
+
let s = RString::from_value(rb_self)
|
|
156
|
+
.ok_or_else(|| Error::Runtime("Failed to create new instance of ImageString".into()))?;
|
|
157
|
+
s.ivar_set("@width", ruby.integer_from_u64(width.into()))?;
|
|
158
|
+
s.ivar_set("@height", ruby.integer_from_u64(height.into()))?;
|
|
159
|
+
s.freeze();
|
|
160
|
+
|
|
161
|
+
Ok(())
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
impl Entry for ImageString {
|
|
166
|
+
type PrivateData = ();
|
|
167
|
+
|
|
168
|
+
fn get(
|
|
169
|
+
obj: Value,
|
|
170
|
+
view: &mut Builder<Self::PrivateData>,
|
|
171
|
+
flags: &Flags,
|
|
172
|
+
) -> std::result::Result<(), magnus::Error> {
|
|
173
|
+
let ruby = Ruby::get_with(obj);
|
|
174
|
+
if flags.has_writable() {
|
|
175
|
+
return Err(magnus::Error::new(
|
|
176
|
+
ruby.exception_arg_error(),
|
|
177
|
+
"Cannot be writable",
|
|
178
|
+
));
|
|
179
|
+
}
|
|
180
|
+
if flags.has_indirect() {
|
|
181
|
+
return Err(magnus::Error::new(
|
|
182
|
+
ruby.exception_arg_error(),
|
|
183
|
+
"Cannot be indirect",
|
|
184
|
+
));
|
|
185
|
+
}
|
|
186
|
+
let Some(rstring) = RString::from_value(obj) else {
|
|
187
|
+
return Err(magnus::Error::new(
|
|
188
|
+
ruby.exception_type_error(),
|
|
189
|
+
"Invalid type",
|
|
190
|
+
));
|
|
191
|
+
};
|
|
192
|
+
let width: isize = rstring.ivar_get("@width")?;
|
|
193
|
+
let height: isize = rstring.ivar_get("@height")?;
|
|
194
|
+
unsafe { view.data(rstring.as_slice()) }
|
|
195
|
+
.format("C")
|
|
196
|
+
.map_err(|e| e.into_error(&ruby))?
|
|
197
|
+
.shape([height, width, 3]);
|
|
198
|
+
|
|
199
|
+
Ok(())
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
fn is_available(_obj: Value) -> bool {
|
|
203
|
+
true
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
fn decode(ruby: &Ruby, arguments: &[Value]) -> Result<Value> {
|
|
208
|
+
let args = scan_args::<(String,), (), (), (), _, ()>(arguments)?;
|
|
209
|
+
let (width, height, punch) = get_kwargs::<_, (u32, u32, f64), (), ()>(
|
|
210
|
+
args.keywords,
|
|
211
|
+
&["width", "height", "punch"],
|
|
212
|
+
&[],
|
|
213
|
+
)?
|
|
214
|
+
.required;
|
|
215
|
+
let blurhash = args.required.0;
|
|
216
|
+
|
|
217
|
+
let decoded = blurhash_core::gpu::decode_gpu(&blurhash, width, height, punch)?;
|
|
218
|
+
|
|
219
|
+
let rstring = ruby.str_from_slice(decoded.as_slice());
|
|
220
|
+
let module: RModule = ruby.class_object().const_get("RuBlurHash")?;
|
|
221
|
+
let img_class: RClass = module.const_get("ImageString")?;
|
|
222
|
+
let instance = img_class.funcall("__send__", ("new", rstring))?;
|
|
223
|
+
ImageString::init(ruby, instance, width, height)?;
|
|
224
|
+
|
|
225
|
+
Ok(instance)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#[magnus::init]
|
|
229
|
+
fn init(ruby: &Ruby) -> Result<()> {
|
|
230
|
+
let module = ruby.define_module("RuBlurHash")?;
|
|
231
|
+
module.define_module_function("encode", function!(encode, -1))?;
|
|
232
|
+
module.define_module_function("decode", function!(decode, -1))?;
|
|
233
|
+
|
|
234
|
+
let image_string = module.define_class("ImageString", ruby.class_string())?;
|
|
235
|
+
let _: Value = image_string
|
|
236
|
+
.singleton_class()?
|
|
237
|
+
.funcall("private", ("new",))?;
|
|
238
|
+
image_string.define_attr("width", Attr::Read)?;
|
|
239
|
+
image_string.define_attr("height", Attr::Read)?;
|
|
240
|
+
register::<ImageString>(image_string)?;
|
|
241
|
+
|
|
242
|
+
Ok(())
|
|
243
|
+
}
|
data/rublurhash.gemspec
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
rust_metadata = JSON.load(`cargo metadata --manifest-path=ext/Cargo.toml --no-deps --format-version=1`)
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "rublurhash"
|
|
7
|
+
s.version = rust_metadata["packages"][0]["version"]
|
|
8
|
+
s.authors = ["Kitaiti Makoto"]
|
|
9
|
+
s.licenses = "MIT"
|
|
10
|
+
s.summary = "Fast BlurHash implementation."
|
|
11
|
+
s.description = "Ruby binding for blurhash-core, a fast Rust implementation of BlurHash."
|
|
12
|
+
s.homepage = "https://gitlab.com/KitaitiMakoto/rublurhash"
|
|
13
|
+
|
|
14
|
+
s.files = Dir.chdir(__dir__) {`git ls-files -z`.split("\x0")}
|
|
15
|
+
s.extensions = ["ext/Cargo.toml"]
|
|
16
|
+
s.test_files = s.files.select {|file| file.start_with?("test/")}
|
|
17
|
+
|
|
18
|
+
s.add_development_dependency "rake"
|
|
19
|
+
s.add_development_dependency "kar"
|
|
20
|
+
s.add_development_dependency "test-unit"
|
|
21
|
+
s.add_development_dependency "test-unit-notify"
|
|
22
|
+
s.add_development_dependency "terminal-notifier" if RUBY_PLATFORM.match? /darwin/
|
|
23
|
+
s.add_development_dependency "rubygems-tasks"
|
|
24
|
+
s.add_development_dependency "steep"
|
|
25
|
+
s.add_development_dependency "ndav"
|
|
26
|
+
s.add_development_dependency "rdoc"
|
|
27
|
+
s.add_development_dependency "yard"
|
|
28
|
+
end
|
data/sig/rublurhash.rbs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module RuBlurHash
|
|
2
|
+
def self?.encode: (Object memory_view_producer, components_x: Integer, components_y: Integer) -> String
|
|
3
|
+
| (Array[Integer] pixels, components_x: Integer, components_y: Integer, width: Integer, height: Integer) -> String
|
|
4
|
+
| (String binary_data, components_x: Integer, components_y: Integer, width: Integer, height: Integer) -> String
|
|
5
|
+
def self?.decode: (String blur_hash_string, width: Integer, height: Integer, punch: Float) -> ImageString
|
|
6
|
+
|
|
7
|
+
# Binary string of image data
|
|
8
|
+
#
|
|
9
|
+
# Also is a MemoryView producer
|
|
10
|
+
class ImageString < String
|
|
11
|
+
attr_reader width: Integer
|
|
12
|
+
attr_reader height: Integer
|
|
13
|
+
end
|
|
14
|
+
end
|
data/sig/test/fiddle.rbs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Test
|
|
2
|
+
module Unit
|
|
3
|
+
class TestCase
|
|
4
|
+
include Assertions
|
|
5
|
+
|
|
6
|
+
def setup: -> void
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module Assertions
|
|
10
|
+
def assert: (bool?) -> void
|
|
11
|
+
| { () -> void } -> void
|
|
12
|
+
def assert_match: (Regexp, String) -> void
|
|
13
|
+
def assert_equal: (untyped, untyped, ?_ToS) -> void
|
|
14
|
+
def assert_raise: { () -> void } -> void
|
|
15
|
+
def assert_path_exist: (path) -> void
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/sig/test/test.rbs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class TestRuBlurHash < Test::Unit::TestCase
|
|
2
|
+
include RuBlurHash
|
|
3
|
+
|
|
4
|
+
RE: Regexp
|
|
5
|
+
ENCODED: String
|
|
6
|
+
|
|
7
|
+
@img: Array[Integer]
|
|
8
|
+
@components_x: Integer
|
|
9
|
+
@components_y: Integer
|
|
10
|
+
|
|
11
|
+
def test_encode: -> void
|
|
12
|
+
def test_encode_memory_view: -> void
|
|
13
|
+
def test_decode: -> void
|
|
14
|
+
def test_image_string: -> void
|
|
15
|
+
def test_module_functions: -> void
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def encoded_length: -> Integer
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class TestPackage < Test::Unit::TestCase
|
|
23
|
+
@gemspec: Gem::Specification
|
|
24
|
+
@pkg_file: String
|
|
25
|
+
|
|
26
|
+
def test_build: -> void
|
|
27
|
+
def test_install: -> void
|
|
28
|
+
end
|
data/test/helper.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require_relative "helper"
|
|
2
|
+
|
|
3
|
+
class TestPackage < Test::Unit::TestCase
|
|
4
|
+
def setup
|
|
5
|
+
@gemspec = Gem::Specification.load("rublurhash.gemspec")
|
|
6
|
+
@pkg_file = File.join("pkg", @gemspec.full_name + ".gem")
|
|
7
|
+
FileUtils.rmtree @pkg_file
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def test_build
|
|
11
|
+
assert system("rake", "build")
|
|
12
|
+
assert File.size(@pkg_file) > 0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_install
|
|
16
|
+
system "rake", "build", exception: true
|
|
17
|
+
gem_file = File.join("pkg", @gemspec.full_name + ".gem")
|
|
18
|
+
Dir.mktmpdir do |dir|
|
|
19
|
+
dir = File.realpath(dir)
|
|
20
|
+
install_dir = File.join(dir, "install")
|
|
21
|
+
FileUtils.mkdir install_dir
|
|
22
|
+
system "gem", "install", "--install-dir", install_dir, "--no-document", gem_file, exception: true
|
|
23
|
+
|
|
24
|
+
assert_path_exist File.join(install_dir, "extensions", Gem::Platform.local.to_s, RbConfig::CONFIG["ruby_version"], @gemspec.full_name, "rublurhash.#{RbConfig::CONFIG['DLEXT']}")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require_relative "helper"
|
|
2
|
+
|
|
3
|
+
class TestRuBlurHash < Test::Unit::TestCase
|
|
4
|
+
include RuBlurHash
|
|
5
|
+
|
|
6
|
+
RE = /\A[0-~]+\z/
|
|
7
|
+
ENCODED = "LEHV6nWB2yk8pyo0adR*.7kCMdnj"
|
|
8
|
+
|
|
9
|
+
def setup
|
|
10
|
+
@img = [
|
|
11
|
+
[255, 0, 0],
|
|
12
|
+
[255, 0, 0],
|
|
13
|
+
[255, 0, 0],
|
|
14
|
+
[255, 0, 0]
|
|
15
|
+
].flatten
|
|
16
|
+
@components_x = 4
|
|
17
|
+
@components_y = 3
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_encode
|
|
21
|
+
encoded = encode(@img, width: 2, height: 2, components_x: @components_x, components_y: @components_y)
|
|
22
|
+
|
|
23
|
+
assert do
|
|
24
|
+
encoded.length == encoded_length
|
|
25
|
+
end
|
|
26
|
+
assert_match RE, encoded
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_encode_memory_view
|
|
30
|
+
memview = NDAV.new(Fiddle::Pointer[@img.pack("C*")], shape: [2, 2, 3])
|
|
31
|
+
encoded = encode(memview, components_x: @components_x, components_y: @components_y)
|
|
32
|
+
|
|
33
|
+
assert do
|
|
34
|
+
encoded.length == encoded_length
|
|
35
|
+
end
|
|
36
|
+
assert_match RE, encoded
|
|
37
|
+
assert do
|
|
38
|
+
encode(@img, width: 2, height: 2, components_x: @components_x, components_y: @components_y) == encoded
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_decode
|
|
43
|
+
decoded = decode(ENCODED, width: 32, height: 32, punch: 1.0)
|
|
44
|
+
|
|
45
|
+
assert do
|
|
46
|
+
decoded.bytesize == 32 * 32 * 3
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
img = decoded.unpack("CCC*")
|
|
50
|
+
img.each do |pixel|
|
|
51
|
+
assert do
|
|
52
|
+
pixel > 0
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
assert_equal 32, decoded.width
|
|
57
|
+
assert_equal 32, decoded.height
|
|
58
|
+
|
|
59
|
+
Fiddle::MemoryView.export decoded do |view|
|
|
60
|
+
assert_equal "C", view.format
|
|
61
|
+
assert_equal [32, 32, 3], view.shape
|
|
62
|
+
view.shape[0].times do |i|
|
|
63
|
+
view.shape[1].times do |j|
|
|
64
|
+
view.shape[2].times do |k|
|
|
65
|
+
assert_equal img[i * 96 + j * 3 + k], view[i, j, k], {i:, j:, k:}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
assert_raise do
|
|
72
|
+
decode("!EHVWB2yk8pyo0adR*.7kCMdnj", width: 32, height: 32, punch: 1.0)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
assert_raise do
|
|
76
|
+
decode("LEHV6nWB", width: 8, height: 8, punch: 1.0)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_image_string
|
|
81
|
+
assert_raise do
|
|
82
|
+
ImageString.new("123")
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_module_functions
|
|
87
|
+
assert_equal encode(@img, width: 2, height: 2, components_x: @components_x, components_y: @components_y), RuBlurHash.encode(@img, width: 2, height: 2, components_x: @components_x, components_y: @components_y)
|
|
88
|
+
assert_equal decode(ENCODED, width: 32, height: 32, punch: 1.0), ::RuBlurHash.decode(ENCODED, width: 32, height: 32, punch: 1.0)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def encoded_length
|
|
94
|
+
4 + 2 * @components_x * @components_y
|
|
95
|
+
end
|
|
96
|
+
end
|