smartimage 0.0.1-java → 0.0.2-java
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.
- data/README.markdown +164 -0
- data/VERSION +1 -1
- data/lib/smart_image.rb +68 -12
- data/lib/smart_image/rmagick_canvas.rb +5 -1
- data/lib/smartimage.rb +15 -0
- data/smartimage-java.gemspec +5 -4
- data/smartimage.gemspec +5 -4
- data/spec/smart_image_spec.rb +7 -0
- metadata +5 -4
- data/README.textile +0 -11
data/README.markdown
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
SmartImage
|
|
2
|
+
==========
|
|
3
|
+
|
|
4
|
+
"It's like a Swiss Army Knife for images, but one of those tiny ones you can
|
|
5
|
+
keep on your keychain"
|
|
6
|
+
|
|
7
|
+
SmartImage provides a cross-platform solution for image compositing that works
|
|
8
|
+
on both MRI and JRuby. If using RMagick feels like swatting a fly with a
|
|
9
|
+
nuclear missile, and ImageScience just doesn't get you there, SmartImage is
|
|
10
|
+
hopefully at that sweet spot in the middle.
|
|
11
|
+
|
|
12
|
+
The functionality available in the current version is somewhat limited, but
|
|
13
|
+
should be added easily in the future thanks to a focus on modularity and a
|
|
14
|
+
clear codebase free of lots of platformisms that normally hinder portability.
|
|
15
|
+
|
|
16
|
+
The goal of SmartImage is to support the most common image compositing tasks
|
|
17
|
+
in a Ruby-like API while not becoming bloated and huge like RMagick, and also
|
|
18
|
+
remaining portable across multiple Ruby implementations, including JRuby.
|
|
19
|
+
|
|
20
|
+
Backends
|
|
21
|
+
--------
|
|
22
|
+
|
|
23
|
+
SmartImage works by implementing a platform-specific SmartImage::Canvas class
|
|
24
|
+
that encompasses all of the low level image manipulation primitives. Two such
|
|
25
|
+
canvases are currently available:
|
|
26
|
+
|
|
27
|
+
* SmartImage::RMagickCanvas: a canvas backend based on the RMagick gem
|
|
28
|
+
* SmartImage::JavaCanvas: a canvas backend based on Java AWT/Graphics2D APIs
|
|
29
|
+
|
|
30
|
+
Can it create thumbnails for my Ruby on Rails-based web application?
|
|
31
|
+
--------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
Yes, SmartImage *CAN* create thumbnails for your Ruby on Rails-based web
|
|
34
|
+
application! And it can do it in the most cross-platform manner imaginable!
|
|
35
|
+
If you are looking for a thumbnail solution that will allow you to safely
|
|
36
|
+
migrate your web application to JRuby in the future, look no further than
|
|
37
|
+
SmartImage.
|
|
38
|
+
|
|
39
|
+
To use SmartImage in your Rails application, simply add the following to
|
|
40
|
+
config/environment.rb:
|
|
41
|
+
|
|
42
|
+
config.gem 'smartimage'
|
|
43
|
+
|
|
44
|
+
(there is an appropriate place to put this line, BTW. The exact location
|
|
45
|
+
is left as an exercise to the reader)
|
|
46
|
+
|
|
47
|
+
That's it! Now wherever you would like to generate thumbnails, use the
|
|
48
|
+
following:
|
|
49
|
+
|
|
50
|
+
SmartImage.thumbnail_file(
|
|
51
|
+
"path/to/input.jpg",
|
|
52
|
+
"path/to/output.jpg",
|
|
53
|
+
:width => 69,
|
|
54
|
+
:height => 42
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
This will generate a thumbnail which is at most 69 pixels wide (but could be
|
|
58
|
+
smaller) and at most 42 pixels tall (but again, could be smaller). It looks
|
|
59
|
+
at the file extension to determine the output format. We specified a .jpg
|
|
60
|
+
so it will output a JPEG encoded image.
|
|
61
|
+
|
|
62
|
+
Why could it be smaller, you ask? Because SmartImage preserves the aspect
|
|
63
|
+
ratio of the original image by default. SmartImage allows you to set aside
|
|
64
|
+
space of a predetermined width/height, but will ensure images are scaled
|
|
65
|
+
with their aspect ratio preserved.
|
|
66
|
+
|
|
67
|
+
Don't like this behavior? Want to stretch out your thumbnails all weird?
|
|
68
|
+
Just turn it off:
|
|
69
|
+
|
|
70
|
+
SmartImage.thumbnail_file(
|
|
71
|
+
"path/to/input.jpg",
|
|
72
|
+
"path/to/output.jpg",
|
|
73
|
+
:width => 69,
|
|
74
|
+
:height => 42,
|
|
75
|
+
:preserve_aspect_ratio => false
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
Tada! Stretched-out images! Yay!
|
|
79
|
+
|
|
80
|
+
What if I want to work with raw image data instead of files?
|
|
81
|
+
------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
SmartImage provides both file-based and data-based methods for every API. All
|
|
84
|
+
the APIs are the same, except file-based APIs have "_file" on the end.
|
|
85
|
+
|
|
86
|
+
For example, above we used the SmartImage.thumbnail_file API. However, there's
|
|
87
|
+
also a SmartImage.thumbnail API that works on raw image data:
|
|
88
|
+
|
|
89
|
+
thumbnail = SmartImage.thumbnail image, :width => 69,
|
|
90
|
+
:height => 42,
|
|
91
|
+
:format => :jpg
|
|
92
|
+
|
|
93
|
+
This API produces a thumbnail in-memory from the given input image, also
|
|
94
|
+
in-memory. We've requested a .jpg thumbnail, with a max width of 69 and
|
|
95
|
+
a max height of 42.
|
|
96
|
+
|
|
97
|
+
If an image format isn't specified, the default is PNG.
|
|
98
|
+
|
|
99
|
+
What other APIs are available?
|
|
100
|
+
------------------------------
|
|
101
|
+
|
|
102
|
+
SmartImage allows you to successively manipulate an image buffer. Here's an
|
|
103
|
+
example and below is the deconstruction:
|
|
104
|
+
|
|
105
|
+
SmartImage.new(69, 42) do |image|
|
|
106
|
+
image.composite_file 'mongoose.jpg', :width => 115,
|
|
107
|
+
:height => 95,
|
|
108
|
+
:preserve_aspect_ratio => false
|
|
109
|
+
|
|
110
|
+
image.alpha_mask_file 'mask.png'
|
|
111
|
+
image.composite_file 'overlay.png'
|
|
112
|
+
image.write 'output.png'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
The first thing to notice is that SmartImage.new takes a width, a height, and
|
|
116
|
+
a block. Creating a new SmartImage makes a new image "canvas" that you can
|
|
117
|
+
draw to. However, all the drawing must take place within the block. You
|
|
118
|
+
can't do things with SmartImages outside the block. The block yields you
|
|
119
|
+
a SmartImage object that you can manipulate willy nilly within the block.
|
|
120
|
+
But after the block, it's kaput, sorry. This is because certain silly C
|
|
121
|
+
extensions lack the ability to garbage collect memory safely and require safe
|
|
122
|
+
allocation and deallocation of memory.
|
|
123
|
+
|
|
124
|
+
The first thing we do is composite an image file onto the buffer. Just like
|
|
125
|
+
the SmartImage.thumbnail_file method we give it an options has with a width,
|
|
126
|
+
height, and aspect ratio preservation options.
|
|
127
|
+
|
|
128
|
+
After that an alpha mask is applied. SmartImage supports applying alpha masks
|
|
129
|
+
in the form of grayscale images where white is opaque and black is transparent.
|
|
130
|
+
|
|
131
|
+
After that, a glossy overlay is composited over the top of the canvas.
|
|
132
|
+
|
|
133
|
+
When it's all done, we write to an output file. We've specified 'output.png'
|
|
134
|
+
so it will write a PNG image to the given file.
|
|
135
|
+
|
|
136
|
+
Doesn't RMagick leak memory?
|
|
137
|
+
----------------------------
|
|
138
|
+
|
|
139
|
+
It does if you use it wrong. There are experimental attempts to safely garbage
|
|
140
|
+
collect RMagick images. However, your best bet is to work with an API which is
|
|
141
|
+
designed so you can't leak memory, which is exactly what SmartImage provides.
|
|
142
|
+
|
|
143
|
+
SmartImage would be over 9000 times better if it...
|
|
144
|
+
---------------------------------------------------
|
|
145
|
+
|
|
146
|
+
Please fork me! I'm sure there's a whole world of better backends that
|
|
147
|
+
SmartImage could be using on MRI-like interpreters than RMagick. Imagine a
|
|
148
|
+
lightweight FFI wrapper to libfreeimage or something to that effect.
|
|
149
|
+
|
|
150
|
+
If there's something SmartImage doesn't do that you'd like it do to, please
|
|
151
|
+
send me a pull request. Just keep in mind that cross-implementation
|
|
152
|
+
consistency is a major focus of SmartImage, so if you implement a new image
|
|
153
|
+
backend it should implement all methods of SmartImage::BaseCanvas, and if you
|
|
154
|
+
wish to add a new method to either SmartImage or SmartImage::BaseCanvas it
|
|
155
|
+
should work across all implementations.
|
|
156
|
+
|
|
157
|
+
Credits
|
|
158
|
+
-------
|
|
159
|
+
|
|
160
|
+
SmartImage assumes your Ruby interpreter supports the absurdly powerful RMagick
|
|
161
|
+
library, unless you're running JRuby, in which case it uses the absurdly
|
|
162
|
+
powerful Java Graphics2D library and AWT.
|
|
163
|
+
|
|
164
|
+
[Mongoose courtesy Wikimedia Commons](http://en.wikipedia.org/wiki/File:Mongoose.jpg)
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.2
|
data/lib/smart_image.rb
CHANGED
|
@@ -32,6 +32,71 @@ class SmartImage
|
|
|
32
32
|
def file_info(path)
|
|
33
33
|
info File.read(path)
|
|
34
34
|
end
|
|
35
|
+
|
|
36
|
+
# Generate a thumbnail from the given image data
|
|
37
|
+
# Options:
|
|
38
|
+
# * width: max width of the image, or explicit width if not preserving
|
|
39
|
+
# aspect ratio
|
|
40
|
+
# * height: ditto, except for height of course
|
|
41
|
+
# * preserve_aspect_ratio: if true, ensure image fits within the given
|
|
42
|
+
# width/height restraints.
|
|
43
|
+
# * format: file extension you'd ordinarily apply to an output file of
|
|
44
|
+
# the type you desire. Supported formats are :jpg, :png, and :gif
|
|
45
|
+
# (default :png)
|
|
46
|
+
def thumbnail(data, options = {})
|
|
47
|
+
source_info = info data
|
|
48
|
+
|
|
49
|
+
opts = {
|
|
50
|
+
:width => source_info.width,
|
|
51
|
+
:height => source_info.height,
|
|
52
|
+
:preserve_aspect_ratio => true,
|
|
53
|
+
:format => :png
|
|
54
|
+
}.merge(options)
|
|
55
|
+
|
|
56
|
+
width, height = calculate_aspect_ratio source_info, opts
|
|
57
|
+
|
|
58
|
+
# Set res so we can assign it within the SmartImage.new block
|
|
59
|
+
res = nil
|
|
60
|
+
|
|
61
|
+
SmartImage.new(width, height) do |image|
|
|
62
|
+
image.composite data, :width => width,
|
|
63
|
+
:height => height,
|
|
64
|
+
:preserve_aspect_ratio => false
|
|
65
|
+
|
|
66
|
+
res = image.encode opts[:format]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
res
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Generate a thumbnail file from a given input file
|
|
73
|
+
# Accepts the same options as SmartImage.thumbnail
|
|
74
|
+
def thumbnail_file(input_path, output_path, options = {})
|
|
75
|
+
opts = {
|
|
76
|
+
:format => File.extname(output_path).sub(/^\./, '')
|
|
77
|
+
}.merge(options)
|
|
78
|
+
|
|
79
|
+
data = SmartImage.thumbnail File.read(input_path), options
|
|
80
|
+
File.open(output_path, 'w') { |file| file << data }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Solve aspect ratio constraints based on source image info and
|
|
84
|
+
# a given options hash. This is mostly an internal method but
|
|
85
|
+
# if you find it useful knock yourself out.
|
|
86
|
+
def calculate_aspect_ratio(info, options)
|
|
87
|
+
if options[:preserve_aspect_ratio]
|
|
88
|
+
composited_size = SmartImage::RatioCalculator.new(
|
|
89
|
+
:source_width => info.width,
|
|
90
|
+
:source_height => info.height,
|
|
91
|
+
:dest_width => Integer(options[:width]),
|
|
92
|
+
:dest_height => Integer(options[:height])
|
|
93
|
+
).size
|
|
94
|
+
|
|
95
|
+
return composited_size.width, composited_size.height
|
|
96
|
+
else
|
|
97
|
+
return options[:width], options[:height]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
35
100
|
end
|
|
36
101
|
|
|
37
102
|
# Create a new SmartImage of the given width and height. Always takes a
|
|
@@ -55,6 +120,8 @@ class SmartImage
|
|
|
55
120
|
@canvas = DeadCanvas.new
|
|
56
121
|
end
|
|
57
122
|
|
|
123
|
+
# After the SmartImage#initialize block completes, the canvas is destroyed
|
|
124
|
+
# and replaced with a DeadCanvas that doesn't let you do anything
|
|
58
125
|
class DeadCanvas
|
|
59
126
|
def method_missing(*args)
|
|
60
127
|
raise ArgumentError, "your image exists only within the SmartImage.new block"
|
|
@@ -81,18 +148,7 @@ class SmartImage
|
|
|
81
148
|
:preserve_aspect_ratio => true
|
|
82
149
|
}.merge(options)
|
|
83
150
|
|
|
84
|
-
|
|
85
|
-
composited_size = SmartImage::RatioCalculator.new(
|
|
86
|
-
:source_width => info.width,
|
|
87
|
-
:source_height => info.height,
|
|
88
|
-
:dest_width => Integer(opts[:width]),
|
|
89
|
-
:dest_height => Integer(opts[:height])
|
|
90
|
-
).size
|
|
91
|
-
|
|
92
|
-
dest_width, dest_height = composited_size.width, composited_size.height
|
|
93
|
-
else
|
|
94
|
-
dest_width, dest_height = opts[:width], opts[:height]
|
|
95
|
-
end
|
|
151
|
+
dest_width, dest_height = self.class.calculate_aspect_ratio info, opts
|
|
96
152
|
|
|
97
153
|
@canvas.composite data, :width => Integer(dest_width),
|
|
98
154
|
:height => Integer(dest_height),
|
|
@@ -28,7 +28,11 @@ class SmartImage
|
|
|
28
28
|
}.merge(options)
|
|
29
29
|
|
|
30
30
|
image.thumbnail! opts[:width], opts[:height]
|
|
31
|
-
|
|
31
|
+
begin
|
|
32
|
+
@canvas.composite! image, opts[:x], opts[:y], OverCompositeOp
|
|
33
|
+
ensure
|
|
34
|
+
image.destroy!
|
|
35
|
+
end
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
# Load the given file as an alpha mask for the image
|
data/lib/smartimage.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Maybe you want to:
|
|
2
|
+
#
|
|
3
|
+
# require 'smartimage'
|
|
4
|
+
#
|
|
5
|
+
# Instead of:
|
|
6
|
+
#
|
|
7
|
+
# require 'smart_image'
|
|
8
|
+
#
|
|
9
|
+
# Yes SmartImage is CamelCase and smart_image is the String#underscore of
|
|
10
|
+
# that, but seriously, shouldn't require 'smartimage' Just Work?
|
|
11
|
+
#
|
|
12
|
+
# Yes, yes it should...
|
|
13
|
+
#
|
|
14
|
+
|
|
15
|
+
require 'smart_image'
|
data/smartimage-java.gemspec
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |s|
|
|
7
7
|
s.name = %q{smartimage}
|
|
8
|
-
s.version = "0.0.
|
|
8
|
+
s.version = "0.0.2"
|
|
9
9
|
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
11
|
s.authors = ["Tony Arcieri"]
|
|
12
|
-
s.date = %q{2010-03-
|
|
12
|
+
s.date = %q{2010-03-30}
|
|
13
13
|
s.description = %q{ SmartImage provides a cross-platform solution for image compositing that works on both MRI and JRuby.
|
|
14
14
|
If using RMagick feels like swatting a fly with a nucler missile, and ImageScience just doesn't get
|
|
15
15
|
you there, SmartImage is hopefully at that sweet spot in the middle
|
|
@@ -17,13 +17,13 @@ Gem::Specification.new do |s|
|
|
|
17
17
|
s.email = %q{tony@medioh.com}
|
|
18
18
|
s.extra_rdoc_files = [
|
|
19
19
|
"LICENSE",
|
|
20
|
-
"README.
|
|
20
|
+
"README.markdown"
|
|
21
21
|
]
|
|
22
22
|
s.files = [
|
|
23
23
|
".document",
|
|
24
24
|
".gitignore",
|
|
25
25
|
"LICENSE",
|
|
26
|
-
"README.
|
|
26
|
+
"README.markdown",
|
|
27
27
|
"Rakefile",
|
|
28
28
|
"VERSION",
|
|
29
29
|
"lib/smart_image.rb",
|
|
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
|
|
|
31
31
|
"lib/smart_image/java_canvas.rb",
|
|
32
32
|
"lib/smart_image/ratio_calculator.rb",
|
|
33
33
|
"lib/smart_image/rmagick_canvas.rb",
|
|
34
|
+
"lib/smartimage.rb",
|
|
34
35
|
"smartimage-java.gemspec",
|
|
35
36
|
"smartimage.gemspec",
|
|
36
37
|
"spec/fixtures/mask.png",
|
data/smartimage.gemspec
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |s|
|
|
7
7
|
s.name = %q{smartimage}
|
|
8
|
-
s.version = "0.0.
|
|
8
|
+
s.version = "0.0.2"
|
|
9
9
|
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
11
|
s.authors = ["Tony Arcieri"]
|
|
12
|
-
s.date = %q{2010-03-
|
|
12
|
+
s.date = %q{2010-03-30}
|
|
13
13
|
s.description = %q{ SmartImage provides a cross-platform solution for image compositing that works on both MRI and JRuby.
|
|
14
14
|
If using RMagick feels like swatting a fly with a nucler missile, and ImageScience just doesn't get
|
|
15
15
|
you there, SmartImage is hopefully at that sweet spot in the middle
|
|
@@ -17,13 +17,13 @@ Gem::Specification.new do |s|
|
|
|
17
17
|
s.email = %q{tony@medioh.com}
|
|
18
18
|
s.extra_rdoc_files = [
|
|
19
19
|
"LICENSE",
|
|
20
|
-
"README.
|
|
20
|
+
"README.markdown"
|
|
21
21
|
]
|
|
22
22
|
s.files = [
|
|
23
23
|
".document",
|
|
24
24
|
".gitignore",
|
|
25
25
|
"LICENSE",
|
|
26
|
-
"README.
|
|
26
|
+
"README.markdown",
|
|
27
27
|
"Rakefile",
|
|
28
28
|
"VERSION",
|
|
29
29
|
"lib/smart_image.rb",
|
|
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
|
|
|
31
31
|
"lib/smart_image/java_canvas.rb",
|
|
32
32
|
"lib/smart_image/ratio_calculator.rb",
|
|
33
33
|
"lib/smart_image/rmagick_canvas.rb",
|
|
34
|
+
"lib/smartimage.rb",
|
|
34
35
|
"smartimage-java.gemspec",
|
|
35
36
|
"smartimage.gemspec",
|
|
36
37
|
"spec/fixtures/mask.png",
|
data/spec/smart_image_spec.rb
CHANGED
|
@@ -41,4 +41,11 @@ describe SmartImage do
|
|
|
41
41
|
image.write @output_dir + 'alpha_mask.png'
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
|
+
|
|
45
|
+
it "generates thumbnails" do
|
|
46
|
+
output = @output_dir + 'thumbnail.jpg'
|
|
47
|
+
|
|
48
|
+
SmartImage.thumbnail_file @mongoose, output, :width => 115,
|
|
49
|
+
:height => 95
|
|
50
|
+
end
|
|
44
51
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smartimage
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.2
|
|
5
5
|
platform: java
|
|
6
6
|
authors:
|
|
7
7
|
- Tony Arcieri
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2010-03-
|
|
12
|
+
date: 2010-03-30 00:00:00 -06:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
@@ -40,12 +40,12 @@ extensions: []
|
|
|
40
40
|
|
|
41
41
|
extra_rdoc_files:
|
|
42
42
|
- LICENSE
|
|
43
|
-
- README.
|
|
43
|
+
- README.markdown
|
|
44
44
|
files:
|
|
45
45
|
- .document
|
|
46
46
|
- .gitignore
|
|
47
47
|
- LICENSE
|
|
48
|
-
- README.
|
|
48
|
+
- README.markdown
|
|
49
49
|
- Rakefile
|
|
50
50
|
- VERSION
|
|
51
51
|
- lib/smart_image.rb
|
|
@@ -53,6 +53,7 @@ files:
|
|
|
53
53
|
- lib/smart_image/java_canvas.rb
|
|
54
54
|
- lib/smart_image/ratio_calculator.rb
|
|
55
55
|
- lib/smart_image/rmagick_canvas.rb
|
|
56
|
+
- lib/smartimage.rb
|
|
56
57
|
- smartimage-java.gemspec
|
|
57
58
|
- smartimage.gemspec
|
|
58
59
|
- spec/fixtures/mask.png
|
data/README.textile
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
h1. SmartImage
|
|
2
|
-
|
|
3
|
-
Hi. There will be a real README here soon, I promise.
|
|
4
|
-
|
|
5
|
-
h2. Credits
|
|
6
|
-
|
|
7
|
-
SmartImage assumes your Ruby interpreter supports the absurdly powerful RMagick
|
|
8
|
-
library, unless you're running JRuby, in which case it uses the absurdly
|
|
9
|
-
powerful Java Graphics2D library and AWT.
|
|
10
|
-
|
|
11
|
-
Mongoose courtesy Wikimedia Commons: http://en.wikipedia.org/wiki/File:Mongoose.jpg
|