ruby-pixels 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING.GPL +674 -0
- data/COPYING.LESSER +165 -0
- data/lib/pixels.rb +636 -0
- metadata +56 -0
data/COPYING.LESSER
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
GNU LESSER GENERAL PUBLIC LICENSE
|
2
|
+
Version 3, 29 June 2007
|
3
|
+
|
4
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies
|
6
|
+
of this license document, but changing it is not allowed.
|
7
|
+
|
8
|
+
|
9
|
+
This version of the GNU Lesser General Public License incorporates
|
10
|
+
the terms and conditions of version 3 of the GNU General Public
|
11
|
+
License, supplemented by the additional permissions listed below.
|
12
|
+
|
13
|
+
0. Additional Definitions.
|
14
|
+
|
15
|
+
As used herein, "this License" refers to version 3 of the GNU Lesser
|
16
|
+
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
17
|
+
General Public License.
|
18
|
+
|
19
|
+
"The Library" refers to a covered work governed by this License,
|
20
|
+
other than an Application or a Combined Work as defined below.
|
21
|
+
|
22
|
+
An "Application" is any work that makes use of an interface provided
|
23
|
+
by the Library, but which is not otherwise based on the Library.
|
24
|
+
Defining a subclass of a class defined by the Library is deemed a mode
|
25
|
+
of using an interface provided by the Library.
|
26
|
+
|
27
|
+
A "Combined Work" is a work produced by combining or linking an
|
28
|
+
Application with the Library. The particular version of the Library
|
29
|
+
with which the Combined Work was made is also called the "Linked
|
30
|
+
Version".
|
31
|
+
|
32
|
+
The "Minimal Corresponding Source" for a Combined Work means the
|
33
|
+
Corresponding Source for the Combined Work, excluding any source code
|
34
|
+
for portions of the Combined Work that, considered in isolation, are
|
35
|
+
based on the Application, and not on the Linked Version.
|
36
|
+
|
37
|
+
The "Corresponding Application Code" for a Combined Work means the
|
38
|
+
object code and/or source code for the Application, including any data
|
39
|
+
and utility programs needed for reproducing the Combined Work from the
|
40
|
+
Application, but excluding the System Libraries of the Combined Work.
|
41
|
+
|
42
|
+
1. Exception to Section 3 of the GNU GPL.
|
43
|
+
|
44
|
+
You may convey a covered work under sections 3 and 4 of this License
|
45
|
+
without being bound by section 3 of the GNU GPL.
|
46
|
+
|
47
|
+
2. Conveying Modified Versions.
|
48
|
+
|
49
|
+
If you modify a copy of the Library, and, in your modifications, a
|
50
|
+
facility refers to a function or data to be supplied by an Application
|
51
|
+
that uses the facility (other than as an argument passed when the
|
52
|
+
facility is invoked), then you may convey a copy of the modified
|
53
|
+
version:
|
54
|
+
|
55
|
+
a) under this License, provided that you make a good faith effort to
|
56
|
+
ensure that, in the event an Application does not supply the
|
57
|
+
function or data, the facility still operates, and performs
|
58
|
+
whatever part of its purpose remains meaningful, or
|
59
|
+
|
60
|
+
b) under the GNU GPL, with none of the additional permissions of
|
61
|
+
this License applicable to that copy.
|
62
|
+
|
63
|
+
3. Object Code Incorporating Material from Library Header Files.
|
64
|
+
|
65
|
+
The object code form of an Application may incorporate material from
|
66
|
+
a header file that is part of the Library. You may convey such object
|
67
|
+
code under terms of your choice, provided that, if the incorporated
|
68
|
+
material is not limited to numerical parameters, data structure
|
69
|
+
layouts and accessors, or small macros, inline functions and templates
|
70
|
+
(ten or fewer lines in length), you do both of the following:
|
71
|
+
|
72
|
+
a) Give prominent notice with each copy of the object code that the
|
73
|
+
Library is used in it and that the Library and its use are
|
74
|
+
covered by this License.
|
75
|
+
|
76
|
+
b) Accompany the object code with a copy of the GNU GPL and this license
|
77
|
+
document.
|
78
|
+
|
79
|
+
4. Combined Works.
|
80
|
+
|
81
|
+
You may convey a Combined Work under terms of your choice that,
|
82
|
+
taken together, effectively do not restrict modification of the
|
83
|
+
portions of the Library contained in the Combined Work and reverse
|
84
|
+
engineering for debugging such modifications, if you also do each of
|
85
|
+
the following:
|
86
|
+
|
87
|
+
a) Give prominent notice with each copy of the Combined Work that
|
88
|
+
the Library is used in it and that the Library and its use are
|
89
|
+
covered by this License.
|
90
|
+
|
91
|
+
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
92
|
+
document.
|
93
|
+
|
94
|
+
c) For a Combined Work that displays copyright notices during
|
95
|
+
execution, include the copyright notice for the Library among
|
96
|
+
these notices, as well as a reference directing the user to the
|
97
|
+
copies of the GNU GPL and this license document.
|
98
|
+
|
99
|
+
d) Do one of the following:
|
100
|
+
|
101
|
+
0) Convey the Minimal Corresponding Source under the terms of this
|
102
|
+
License, and the Corresponding Application Code in a form
|
103
|
+
suitable for, and under terms that permit, the user to
|
104
|
+
recombine or relink the Application with a modified version of
|
105
|
+
the Linked Version to produce a modified Combined Work, in the
|
106
|
+
manner specified by section 6 of the GNU GPL for conveying
|
107
|
+
Corresponding Source.
|
108
|
+
|
109
|
+
1) Use a suitable shared library mechanism for linking with the
|
110
|
+
Library. A suitable mechanism is one that (a) uses at run time
|
111
|
+
a copy of the Library already present on the user's computer
|
112
|
+
system, and (b) will operate properly with a modified version
|
113
|
+
of the Library that is interface-compatible with the Linked
|
114
|
+
Version.
|
115
|
+
|
116
|
+
e) Provide Installation Information, but only if you would otherwise
|
117
|
+
be required to provide such information under section 6 of the
|
118
|
+
GNU GPL, and only to the extent that such information is
|
119
|
+
necessary to install and execute a modified version of the
|
120
|
+
Combined Work produced by recombining or relinking the
|
121
|
+
Application with a modified version of the Linked Version. (If
|
122
|
+
you use option 4d0, the Installation Information must accompany
|
123
|
+
the Minimal Corresponding Source and Corresponding Application
|
124
|
+
Code. If you use option 4d1, you must provide the Installation
|
125
|
+
Information in the manner specified by section 6 of the GNU GPL
|
126
|
+
for conveying Corresponding Source.)
|
127
|
+
|
128
|
+
5. Combined Libraries.
|
129
|
+
|
130
|
+
You may place library facilities that are a work based on the
|
131
|
+
Library side by side in a single library together with other library
|
132
|
+
facilities that are not Applications and are not covered by this
|
133
|
+
License, and convey such a combined library under terms of your
|
134
|
+
choice, if you do both of the following:
|
135
|
+
|
136
|
+
a) Accompany the combined library with a copy of the same work based
|
137
|
+
on the Library, uncombined with any other library facilities,
|
138
|
+
conveyed under the terms of this License.
|
139
|
+
|
140
|
+
b) Give prominent notice with the combined library that part of it
|
141
|
+
is a work based on the Library, and explaining where to find the
|
142
|
+
accompanying uncombined form of the same work.
|
143
|
+
|
144
|
+
6. Revised Versions of the GNU Lesser General Public License.
|
145
|
+
|
146
|
+
The Free Software Foundation may publish revised and/or new versions
|
147
|
+
of the GNU Lesser General Public License from time to time. Such new
|
148
|
+
versions will be similar in spirit to the present version, but may
|
149
|
+
differ in detail to address new problems or concerns.
|
150
|
+
|
151
|
+
Each version is given a distinguishing version number. If the
|
152
|
+
Library as you received it specifies that a certain numbered version
|
153
|
+
of the GNU Lesser General Public License "or any later version"
|
154
|
+
applies to it, you have the option of following the terms and
|
155
|
+
conditions either of that published version or of any later version
|
156
|
+
published by the Free Software Foundation. If the Library as you
|
157
|
+
received it does not specify a version number of the GNU Lesser
|
158
|
+
General Public License, you may choose any version of the GNU Lesser
|
159
|
+
General Public License ever published by the Free Software Foundation.
|
160
|
+
|
161
|
+
If the Library as you received it specifies that a proxy can decide
|
162
|
+
whether future versions of the GNU Lesser General Public License shall
|
163
|
+
apply, that proxy's public statement of acceptance of any version is
|
164
|
+
permanent authorization for you to choose that version for the
|
165
|
+
Library.
|
data/lib/pixels.rb
ADDED
@@ -0,0 +1,636 @@
|
|
1
|
+
######################################################################
|
2
|
+
# Author(s)::
|
3
|
+
# Dwayne C. Litzenberger (http://www.dlitz.net)
|
4
|
+
######################################################################
|
5
|
+
# Copyright::
|
6
|
+
# Copyright (c) 2009 Dwayne C. Litzenberger <dlitz@dlitz.net>
|
7
|
+
# License::
|
8
|
+
# This file is part of Ruby Pixels.
|
9
|
+
#
|
10
|
+
# Ruby Pixels is free software: you can redistribute it and/or modify it
|
11
|
+
# under the terms of the GNU Lesser General Public License as published
|
12
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
13
|
+
# (at your option) any later version.
|
14
|
+
#
|
15
|
+
# Ruby Pixels is distributed in the hope that it will be useful,
|
16
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
17
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
18
|
+
# GNU Lesser General Public License for more details.
|
19
|
+
#
|
20
|
+
# You should have received a copy of the GNU Lesser General Public
|
21
|
+
# License along with Ruby Pixels. If not, see
|
22
|
+
# <http://www.gnu.org/licenses/>.
|
23
|
+
######################################################################
|
24
|
+
# Homepage:: http://www.dlitz.net/software/ruby-pixels
|
25
|
+
#
|
26
|
+
# The TGA format is documented at several places on the web, such as:
|
27
|
+
# http://local.wasp.uwa.edu.au/~pbourke/dataformats/tga/
|
28
|
+
#
|
29
|
+
|
30
|
+
require 'thread'
|
31
|
+
|
32
|
+
#
|
33
|
+
# Ruby Pixels allows you to read and write RGB/RGBA pixel data stored in
|
34
|
+
# uncompressed, non-interleaved TGA (Targa) files.
|
35
|
+
#
|
36
|
+
# Unlike some other libraries, Ruby Pixels reads and writes one row of pixels
|
37
|
+
# at a time, you can work with several large images at once without running
|
38
|
+
# out of memory.
|
39
|
+
#
|
40
|
+
# = Requirements
|
41
|
+
#
|
42
|
+
# Ruby Pixels needs no external libraries.
|
43
|
+
#
|
44
|
+
# = Limitations
|
45
|
+
#
|
46
|
+
# Ruby Pixels cannot read or write compressed, interleaved, or colour-mapped
|
47
|
+
# images. You may wish to use another tool (e.g. MiniMagick) to convert
|
48
|
+
# to and from other formats.
|
49
|
+
#
|
50
|
+
# Ruby Pixels currently has no support for reading or writing individual
|
51
|
+
# pixels. You need to do it on a row-by-row basis.
|
52
|
+
#
|
53
|
+
# = Example Code
|
54
|
+
#
|
55
|
+
# # invert-image.rb - Invert the colour of an image.
|
56
|
+
# require 'pixels'
|
57
|
+
#
|
58
|
+
# input = Pixels.open_tga("mm/mm-01.tga")
|
59
|
+
# output = Pixels.create_tga("output.tga", input.spec)
|
60
|
+
#
|
61
|
+
# input.each_row_rgb do |in_row, y|
|
62
|
+
# out_row = []
|
63
|
+
# for r, g, b in in_row
|
64
|
+
# out_row << [255-r, 255-g, 255-b]
|
65
|
+
# end
|
66
|
+
# output.put_row_rgb(y, out_row)
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# output.close
|
70
|
+
# input.close
|
71
|
+
#
|
72
|
+
module Pixels
|
73
|
+
|
74
|
+
VERSION = '0.0.1'
|
75
|
+
|
76
|
+
class DataFormatError < StandardError
|
77
|
+
end
|
78
|
+
|
79
|
+
# Open the specified TGA file.
|
80
|
+
#
|
81
|
+
# file_or_path may be a pathname or a file-like object. If it is a
|
82
|
+
# pathname, it is opened for reading.
|
83
|
+
#
|
84
|
+
# Returns an instance of one of TargaBase's children (one of Targa15,
|
85
|
+
# Targa16, Targa24, or Targa32), depending on the format of the specified
|
86
|
+
# file.
|
87
|
+
def self.open_tga(file_or_path)
|
88
|
+
if file_or_path.respond_to?(:read)
|
89
|
+
file = file_or_path
|
90
|
+
else
|
91
|
+
file = File.open(file_or_path, "rb:binary")
|
92
|
+
end
|
93
|
+
|
94
|
+
raw_header = file.read(18)
|
95
|
+
h, r = decode_tga_header(raw_header)
|
96
|
+
case [h[:bits_per_pixel], r[:alpha_depth]]
|
97
|
+
when [16, 0]
|
98
|
+
return Targa15.new(file, h, r, 2)
|
99
|
+
when [16, 1]
|
100
|
+
return Targa16.new(file, h, r, 2)
|
101
|
+
when [24, 0]
|
102
|
+
return Targa24.new(file, h, r, 3)
|
103
|
+
when [32, 0]
|
104
|
+
return Targa24.new(file, h, r, 4)
|
105
|
+
when [32, 8]
|
106
|
+
return Targa32.new(file, h, r, 4)
|
107
|
+
else
|
108
|
+
raise DataFormatError.new(
|
109
|
+
"#{h[:bits_per_pixel]} bpp with #{h[:alpha_depth]}-bit alpha channel not supported")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Create a TGA file according to the given specification, and return an
|
114
|
+
# instance of one of TargaBase's children (one of Targa15, Targa16, Targa24,
|
115
|
+
# or Targa32), depending on the format specification.
|
116
|
+
#
|
117
|
+
# file_or_path may be a pathname or a file-like object. If it is a
|
118
|
+
# pathname, it is opened for reading.
|
119
|
+
#
|
120
|
+
# spec is a Hash containing the following keys:
|
121
|
+
# [:width]
|
122
|
+
# Width of the image in pixels
|
123
|
+
#
|
124
|
+
# [:height]
|
125
|
+
# Height of the image in pixels
|
126
|
+
#
|
127
|
+
# [:color_depth]
|
128
|
+
# Color depth of the image in bits per pixel. This does not include the
|
129
|
+
# bits used to represent the alpha channel (if any).
|
130
|
+
#
|
131
|
+
# Currently, this is always one of [15, 24].
|
132
|
+
#
|
133
|
+
# [:has_alpha]
|
134
|
+
# If the image has an alpha channel, this is true. Otherwise, it is
|
135
|
+
# false. (default false)
|
136
|
+
#
|
137
|
+
# [:origin]
|
138
|
+
# Specifies which pixel appears first in the TGA file. Must be one of
|
139
|
+
# :UPPER_LEFT or :LOWER_LEFT.
|
140
|
+
#
|
141
|
+
# Note: This only affects the TGA file's internal layout.
|
142
|
+
# get_row_rgb(0) always returns the uppermost row in Ruby Pixels.
|
143
|
+
# (default :UPPER_LEFT)
|
144
|
+
def self.create_tga(file_or_path, spec={})
|
145
|
+
spec = {
|
146
|
+
:width => nil,
|
147
|
+
:height => nil,
|
148
|
+
:color_depth => nil,
|
149
|
+
:has_alpha => false,
|
150
|
+
:origin => :UPPER_LEFT,
|
151
|
+
}.merge(spec)
|
152
|
+
|
153
|
+
case [spec[:color_depth], !!spec[:has_alpha]]
|
154
|
+
when [15, true]
|
155
|
+
bpp = 16
|
156
|
+
alpha_depth = 1
|
157
|
+
when [16, false]
|
158
|
+
bpp = 16
|
159
|
+
alpha_depth = 0
|
160
|
+
when [24, false]
|
161
|
+
bpp = 24
|
162
|
+
alpha_depth = 0
|
163
|
+
when [24, true]
|
164
|
+
bpp = 32
|
165
|
+
alpha_depth = 8
|
166
|
+
else
|
167
|
+
raise ArgumentError.new(
|
168
|
+
":depth=#{colour_depth}-bpp with :alpha=#{has_alpha} not supported")
|
169
|
+
end
|
170
|
+
|
171
|
+
image_descriptor = alpha_depth
|
172
|
+
case spec[:origin]
|
173
|
+
when :LOWER_LEFT
|
174
|
+
# Do nothing
|
175
|
+
when :UPPER_LEFT
|
176
|
+
image_descriptor |= 0x20
|
177
|
+
else
|
178
|
+
raise ArgumentError.new(":origin must be :LOWER_LEFT or :UPPER_LEFT")
|
179
|
+
end
|
180
|
+
|
181
|
+
raw_header = [
|
182
|
+
0, # idlength
|
183
|
+
0, # colourmap type
|
184
|
+
2, # data type: Uncompressed RGB(A)
|
185
|
+
0, # colourmap_origin
|
186
|
+
0, # colourmap_length
|
187
|
+
0, # colourmap_depth
|
188
|
+
0, # x_origin
|
189
|
+
0, # y_origin
|
190
|
+
spec[:width],
|
191
|
+
spec[:height],
|
192
|
+
bpp,
|
193
|
+
image_descriptor,
|
194
|
+
].pack("CCCvvCvvvvCC")
|
195
|
+
|
196
|
+
h, r = decode_tga_header(raw_header)
|
197
|
+
|
198
|
+
if file_or_path.respond_to?(:write)
|
199
|
+
file = file_or_path
|
200
|
+
else
|
201
|
+
file = File.open(file_or_path, "w+b:binary")
|
202
|
+
end
|
203
|
+
|
204
|
+
file.write(raw_header)
|
205
|
+
file.seek(0, IO::SEEK_SET)
|
206
|
+
return open_tga(file)
|
207
|
+
end
|
208
|
+
|
209
|
+
def self.decode_tga_header(raw_header) #:nodoc:
|
210
|
+
h = {}
|
211
|
+
h[:idlength], h[:colourmap_type], h[:data_type_code], h[:colourmap_origin],
|
212
|
+
h[:colourmap_length], h[:colourmap_depth], h[:x_origin], h[:y_origin],
|
213
|
+
h[:width], h[:height], h[:bits_per_pixel], h[:image_descriptor] =
|
214
|
+
raw_header.unpack("CCCvvCvvvvCC")
|
215
|
+
|
216
|
+
# Data type
|
217
|
+
if h[:data_type_code] != 2
|
218
|
+
raise DataFormatError.new(
|
219
|
+
"Only uncompressed, unmapped RGB or RGBA data is supported (is this a TGA file?)")
|
220
|
+
end
|
221
|
+
|
222
|
+
r = {}
|
223
|
+
r[:width] = h[:width]
|
224
|
+
r[:height] = h[:height]
|
225
|
+
r[:image_data_offset] = 18 + h[:idlength] + h[:colourmap_length]
|
226
|
+
|
227
|
+
r[:bpp] = h[:bits_per_pixel]
|
228
|
+
r[:alpha_depth] = h[:image_descriptor] & 0xf
|
229
|
+
r[:color_depth] = h[:bits_per_pixel] - r[:alpha_depth]
|
230
|
+
r[:origin] = (h[:image_descriptor] & 0x20 != 0) ? :UPPER_LEFT : :LOWER_LEFT
|
231
|
+
|
232
|
+
# Interleaving
|
233
|
+
if (h[:image_descriptor] & 0xc0) != 0
|
234
|
+
raise DataFormatError.new("Interleaved data not supported")
|
235
|
+
end
|
236
|
+
|
237
|
+
return [h, r]
|
238
|
+
end
|
239
|
+
|
240
|
+
# Abstract class
|
241
|
+
class TargaBase
|
242
|
+
# Width of the image (pixels)
|
243
|
+
attr_reader :width
|
244
|
+
|
245
|
+
# Height of the image (pixels)
|
246
|
+
attr_reader :height
|
247
|
+
|
248
|
+
# Number of bits used to store each pixel
|
249
|
+
attr_reader :bpp
|
250
|
+
|
251
|
+
# Color-depth of the image (bits per pixel)
|
252
|
+
attr_reader :color_depth
|
253
|
+
|
254
|
+
# Bit-depth of the alpha channel (bits per pixel)
|
255
|
+
attr_reader :alpha_depth
|
256
|
+
|
257
|
+
# Indicates which pixel appears first in the TGA file.
|
258
|
+
# One of :UPPER_LEFT or :LOWER_LEFT.
|
259
|
+
attr_reader :origin
|
260
|
+
|
261
|
+
# Do not instantiate this object directly. Use from_file.
|
262
|
+
def initialize(file, header, instance_vars, bytes_per_pixel)
|
263
|
+
@mutex = Mutex.new # Obtain this lock whenever you use @file
|
264
|
+
@file = file
|
265
|
+
@header = header
|
266
|
+
for k, v in instance_vars
|
267
|
+
instance_variable_set("@#{k.to_s}", v)
|
268
|
+
end
|
269
|
+
@bytes_per_pixel = bytes_per_pixel
|
270
|
+
@bytes_per_row = bytes_per_pixel * @width
|
271
|
+
end
|
272
|
+
|
273
|
+
# Return a Hash containing the file format specification, which can be used as the "spec"
|
274
|
+
# parameter in Pixels::create_tga.
|
275
|
+
def spec
|
276
|
+
return {
|
277
|
+
:width => width,
|
278
|
+
:height => height,
|
279
|
+
:color_depth => color_depth,
|
280
|
+
:has_alpha => has_alpha?,
|
281
|
+
:origin => origin,
|
282
|
+
}
|
283
|
+
end
|
284
|
+
|
285
|
+
# Return a string containing the raw bytes from the row at the
|
286
|
+
# specified y-coordinate.
|
287
|
+
#
|
288
|
+
# You probably want to use get_row_rgb or get_row_rgba instead.
|
289
|
+
def read_row_bytes(y)
|
290
|
+
@mutex.synchronize {
|
291
|
+
@file.seek(row_offset(y), IO::SEEK_SET)
|
292
|
+
return @file.read(@bytes_per_row)
|
293
|
+
}
|
294
|
+
end
|
295
|
+
|
296
|
+
# Write a string containing the raw bytes for a row Return a string containing the raw bytes from the row at the
|
297
|
+
# specified y-coordinate.
|
298
|
+
#
|
299
|
+
# You probably want to use put_row_rgb or put_row_rgba instead.
|
300
|
+
def write_row_bytes(y, raw_data)
|
301
|
+
if raw_data.length != @bytes_per_row
|
302
|
+
raise ArgumentError.new("raw_data.length was #{raw_data.length}, expected #{@bytes_per_row}")
|
303
|
+
end
|
304
|
+
@mutex.synchronize {
|
305
|
+
@file.seek(row_offset(y), IO::SEEK_SET)
|
306
|
+
return @file.write(raw_data)
|
307
|
+
}
|
308
|
+
end
|
309
|
+
|
310
|
+
# Close the underlying file.
|
311
|
+
def close
|
312
|
+
@mutex.synchronize {
|
313
|
+
@file.close
|
314
|
+
@file = nil
|
315
|
+
@mutex = nil
|
316
|
+
}
|
317
|
+
end
|
318
|
+
|
319
|
+
# Iterate through each row of the image, representing each pixel as an RGB
|
320
|
+
# value.
|
321
|
+
#
|
322
|
+
# For each y-coordinate in the image, this method calls the given block
|
323
|
+
# with two arguments: get_row_rgb(y) and y.
|
324
|
+
#
|
325
|
+
# If no block is provided, an Enumerator is returned.
|
326
|
+
def each_row_rgb
|
327
|
+
return Enumerable::Enumerator.new(self, :each_row_rgb) unless block_given?
|
328
|
+
for y in (0..@height-1)
|
329
|
+
yield get_row_rgb(y), y
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Iterate through each row of the image, representing each pixel as an
|
334
|
+
# RGBA value.
|
335
|
+
#
|
336
|
+
# For each y-coordinate in the image, this method calls the given block
|
337
|
+
# with two arguments: get_row_rgba(y) and y.
|
338
|
+
#
|
339
|
+
# If no block is provided, an Enumerator is returned.
|
340
|
+
def each_row_rgba
|
341
|
+
return Enumerable::Enumerator.new(self, :each_row_rgba) unless block_given?
|
342
|
+
for y in (0..@height-1)
|
343
|
+
yield get_row_rgba(y), y
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
# Return the row of pixels having the specified y-coordinate. The row is
|
348
|
+
# represented as an array of [r, g, b] values for each pixel in the row.
|
349
|
+
#
|
350
|
+
# Each r, g, b value is an integer between 0 and 255.
|
351
|
+
def get_row_rgb(y)
|
352
|
+
return get_row(y).map { |color| rgb_from_color(color) }
|
353
|
+
end
|
354
|
+
|
355
|
+
# Return the row of pixels having the specified y-coordinate. The row is
|
356
|
+
# represented as an array of [r, g, b, a] values for each pixel in the row.
|
357
|
+
#
|
358
|
+
# Each r, g, b, a value is an integer between 0 and 255.
|
359
|
+
def get_row_rgba(y)
|
360
|
+
return get_row(y).map { |color| rgba_from_color(color) }
|
361
|
+
end
|
362
|
+
|
363
|
+
# Replace the row of pixels having the specified y-coordinate. The row is
|
364
|
+
# represented as an array of [r, g, b] values for each pixel in the row.
|
365
|
+
#
|
366
|
+
# Each r, g, b value is an integer between 0 and 255.
|
367
|
+
def put_row_rgb(y, row_rgb)
|
368
|
+
return put_row(y, row_rgb.map { |r, g, b| color_from_rgb(r, g, b) })
|
369
|
+
end
|
370
|
+
|
371
|
+
# Replace the row of pixels having the specified y-coordinate. The row is
|
372
|
+
# represented as an array of [r, g, b, a] values for each pixel in the row.
|
373
|
+
#
|
374
|
+
# Each r, g, b, a value is an integer between 0 and 255.
|
375
|
+
def put_row_rgba(y, row_rgba)
|
376
|
+
return put_row(y, row_rgba.map { |r, g, b, a| color_from_rgba(r, g, b, a) })
|
377
|
+
end
|
378
|
+
|
379
|
+
protected
|
380
|
+
|
381
|
+
# Return the offset in the file where the specified row can be found.
|
382
|
+
def row_offset(y)
|
383
|
+
if y < 0 or y >= @height
|
384
|
+
raise ArgumentError.new("y-coordinate #{y} out of range")
|
385
|
+
end
|
386
|
+
|
387
|
+
# Flip the vertical axis when (0, 0) is in the lower-left
|
388
|
+
# corner of the image.
|
389
|
+
if @origin == :LOWER_LEFT
|
390
|
+
y = (@height-1) - y
|
391
|
+
end
|
392
|
+
|
393
|
+
return @image_data_offset + @bytes_per_row * y
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
# Mix-in module for image types having no alpha channel.
|
398
|
+
#
|
399
|
+
# It makes has_alpha? return false, and it emulates rgba_from_color and
|
400
|
+
# color_from_rgba.
|
401
|
+
module NoAlphaChannel
|
402
|
+
|
403
|
+
# Return true of the image has an alpha channel. Otherwise, return false.
|
404
|
+
def has_alpha?
|
405
|
+
false
|
406
|
+
end
|
407
|
+
|
408
|
+
# Given an integer colour value, return separate [r, g, b, 255] values.
|
409
|
+
#
|
410
|
+
# This is a wrapper around rgb_from_color. The alpha channel is always
|
411
|
+
# set fully opaque.
|
412
|
+
def rgba_from_color(color)
|
413
|
+
return rgb_from_color(color) + [255]
|
414
|
+
end
|
415
|
+
|
416
|
+
# Given separate [r, g, b, a] values, return the integer colour value.
|
417
|
+
#
|
418
|
+
# This is a wrapper around color_from_rgb. The alpha channel is ignored.
|
419
|
+
def color_from_rgba(r, g, b, a)
|
420
|
+
return color_from_rgb(r, g, b)
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
# Mix-in module for image types having an alpha channel.
|
425
|
+
#
|
426
|
+
# It makes has_alpha? return true, and it emulates rgb_from_color and
|
427
|
+
# color_from_rgb.
|
428
|
+
module HasAlphaChannel
|
429
|
+
# Return true of the image has an alpha channel. Otherwise, return false.
|
430
|
+
def has_alpha?
|
431
|
+
true
|
432
|
+
end
|
433
|
+
|
434
|
+
# Given an integer colour value, return separate [r, g, b] values.
|
435
|
+
#
|
436
|
+
# This is a wrapper around rgba_from_color. The alpha channel is ignored.
|
437
|
+
def rgb_from_color(color)
|
438
|
+
return rgba_from_color(color)[0..2]
|
439
|
+
end
|
440
|
+
|
441
|
+
# Given separate [r, g, b] values, return the integer colour value.
|
442
|
+
#
|
443
|
+
# This is a wrapper around color_from_rgba. The alpha channel is always
|
444
|
+
# set fully opaque.
|
445
|
+
def color_from_rgb(r, g, b)
|
446
|
+
return color_from_rgba(r, g, b, 255)
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
class Targa15 < TargaBase
|
451
|
+
include NoAlphaChannel
|
452
|
+
|
453
|
+
# You probably want to use TargaBase#get_row_rgb or TargaBase#get_row_rgba instead.
|
454
|
+
def get_row(y)
|
455
|
+
bytes = read_row_bytes(y)
|
456
|
+
row = []
|
457
|
+
for offset in (0..@width*@bytes_per_pixel-1).step(@bytes_per_pixel)
|
458
|
+
v, = bytes[offset,2].unpack("v")
|
459
|
+
row << (v & 0x7fff)
|
460
|
+
end
|
461
|
+
return row
|
462
|
+
end
|
463
|
+
|
464
|
+
# You probably want to use TargaBase#put_row_rgb or TargaBase#put_row_rgba instead.
|
465
|
+
def put_row(y, row)
|
466
|
+
bytes = row.pack("v" * row.length)
|
467
|
+
write_row_bytes(y, bytes)
|
468
|
+
end
|
469
|
+
|
470
|
+
# Given a 15-bit integer colour value, return separate [r, g, b] values.
|
471
|
+
#
|
472
|
+
# Each r, g, b value is an integer between 0 and 255.
|
473
|
+
def rgb_from_color(color)
|
474
|
+
# Extract 5 bits-per-channel values
|
475
|
+
b5 = color & 0x1f
|
476
|
+
g5 = (color >> 5) & 0x1f
|
477
|
+
r5 = (color >> 10) & 0x1f
|
478
|
+
|
479
|
+
# Convert 5 bits-per-channel to 8 bits-per-channel
|
480
|
+
r8 = r5 * 255 / 31
|
481
|
+
g8 = g5 * 255 / 31
|
482
|
+
b8 = b5 * 255 / 31
|
483
|
+
return [r, g, b]
|
484
|
+
end
|
485
|
+
|
486
|
+
# Return a 15-bit integer pixel value given separate red, green, and blue values.
|
487
|
+
#
|
488
|
+
# Each r, g, b value is an integer between 0 and 255.
|
489
|
+
def color_from_rgb(r, g, b)
|
490
|
+
# Convert 8 bits-per-channel to 5 bits-per-channel
|
491
|
+
r5 = (r.to_i >> 3) & 0x1f
|
492
|
+
g5 = (g.to_i >> 3) & 0x1f
|
493
|
+
b5 = (b.to_i >> 3) & 0x1f
|
494
|
+
return (b5 << 10) | (g5 << 5) | r5
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
class Targa16 < TargaBase
|
499
|
+
include HasAlphaChannel
|
500
|
+
|
501
|
+
# You probably want to use TargaBase#get_row_rgb or TargaBase#get_row_rgba instead.
|
502
|
+
def get_row(y)
|
503
|
+
bytes = read_row_bytes(y)
|
504
|
+
row = []
|
505
|
+
for offset in (0..@width*@bytes_per_pixel-1).step(@bytes_per_pixel)
|
506
|
+
v, = bytes[offset,2].unpack("v")
|
507
|
+
row << v
|
508
|
+
end
|
509
|
+
return row
|
510
|
+
end
|
511
|
+
|
512
|
+
# You probably want to use TargaBase#put_row_rgb or TargaBase#put_row_rgba instead.
|
513
|
+
def put_row(y, row)
|
514
|
+
bytes = row.pack("v" * row.length)
|
515
|
+
write_row_bytes(y, bytes)
|
516
|
+
end
|
517
|
+
|
518
|
+
# Given a 16-bit integer colour value, return separate [r, g, b, a] values.
|
519
|
+
#
|
520
|
+
# Each r, g, b, a value is an integer between 0 and 255.
|
521
|
+
def rgba_from_color(color)
|
522
|
+
# Extract 5 bits-per-channel values
|
523
|
+
b5 = color & 0x1f
|
524
|
+
g5 = (color >> 5) & 0x1f
|
525
|
+
r5 = (color >> 10) & 0x1f
|
526
|
+
a1 = (color >> 15) & 1
|
527
|
+
|
528
|
+
# Convert 5 bits-per-channel to 8 bits-per-channel
|
529
|
+
r8 = r5 * 255 / 31
|
530
|
+
g8 = g5 * 255 / 31
|
531
|
+
b8 = b5 * 255 / 31
|
532
|
+
a8 = (a1 > 0) ? 255 : 0
|
533
|
+
return [r8, g8, b8, a8]
|
534
|
+
end
|
535
|
+
|
536
|
+
# Return a 16-bit integer pixel value given separate red, green, blue, and alpha values.
|
537
|
+
#
|
538
|
+
# Each r, g, b, a value is an integer between 0 and 255.
|
539
|
+
def color_from_rgba(r, g, b, a)
|
540
|
+
# Convert 8 bits-per-channel to 5 bits-per-channel
|
541
|
+
r5 = (r.to_i >> 3) & 0x1f
|
542
|
+
g5 = (g.to_i >> 3) & 0x1f
|
543
|
+
b5 = (b.to_i >> 3) & 0x1f
|
544
|
+
a1 = (a.to_i >> 7) & 1
|
545
|
+
return (a1 << 15) | (b5 << 10) | (g5 << 5) | r5
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
class Targa24 < TargaBase
|
550
|
+
include NoAlphaChannel
|
551
|
+
|
552
|
+
# You probably want to use TargaBase#get_row_rgb or TargaBase#get_row_rgba instead.
|
553
|
+
def get_row(y)
|
554
|
+
bytes = read_row_bytes(y)
|
555
|
+
row = []
|
556
|
+
for offset in (0..@width*@bytes_per_pixel-1).step(@bytes_per_pixel)
|
557
|
+
v, = (bytes[offset,3] + "\x00").unpack("V")
|
558
|
+
row << (v & 0x00ffffff)
|
559
|
+
end
|
560
|
+
return row
|
561
|
+
end
|
562
|
+
|
563
|
+
# You probably want to use TargaBase#put_row_rgb or TargaBase#put_row_rgba instead.
|
564
|
+
def put_row(y, row)
|
565
|
+
bytes = row.map{|v| [v].pack("V")[0..2]}.join
|
566
|
+
write_row_bytes(y, bytes)
|
567
|
+
end
|
568
|
+
|
569
|
+
# Given a 24-bit integer colour value, return separate [r, g, b] values.
|
570
|
+
#
|
571
|
+
# Each r, g, b value is an integer between 0 and 255.
|
572
|
+
def rgb_from_color(color)
|
573
|
+
# Extract 8-bit-per-channel values
|
574
|
+
b = color & 0xff
|
575
|
+
g = (color >> 8) & 0xff
|
576
|
+
r = (color >> 16) & 0xff
|
577
|
+
return [r, g, b]
|
578
|
+
end
|
579
|
+
|
580
|
+
# Return a 24-bit integer pixel value given separate red, green, and blue values.
|
581
|
+
#
|
582
|
+
# Each r, g, b value is an integer between 0 and 255.
|
583
|
+
def color_from_rgb(r, g, b)
|
584
|
+
# Pack 8-bit-per-channel values
|
585
|
+
return ((r.to_i & 0xff) << 16) |
|
586
|
+
((g.to_i & 0xff) << 8) |
|
587
|
+
(b.to_i & 0xff)
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
class Targa32 < TargaBase
|
592
|
+
include HasAlphaChannel
|
593
|
+
|
594
|
+
# You probably want to use TargaBase#get_row_rgb or TargaBase#get_row_rgba instead.
|
595
|
+
def get_row(y)
|
596
|
+
bytes = read_row_bytes(y)
|
597
|
+
row = []
|
598
|
+
for offset in (0..@width*@bytes_per_pixel-1).step(@bytes_per_pixel)
|
599
|
+
row += bytes[offset,4].unpack("V")
|
600
|
+
end
|
601
|
+
return row
|
602
|
+
end
|
603
|
+
|
604
|
+
# You probably want to use TargaBase#put_row_rgb or TargaBase#put_row_rgba instead.
|
605
|
+
def put_row(y, row)
|
606
|
+
bytes = row.pack("V" * row.length)
|
607
|
+
write_row_bytes(y, bytes)
|
608
|
+
end
|
609
|
+
|
610
|
+
# Given a 16-bit integer colour value, return separate [r, g, b, a] values.
|
611
|
+
#
|
612
|
+
# Each r, g, b, a value is an integer between 0 and 255.
|
613
|
+
def rgba_from_color(color)
|
614
|
+
# Extract 8-bit-per-channel values
|
615
|
+
b = color & 0xff
|
616
|
+
g = (color >> 8) & 0xff
|
617
|
+
r = (color >> 16) & 0xff
|
618
|
+
a = (color >> 24) & 0xff
|
619
|
+
return [r, g, b, a]
|
620
|
+
end
|
621
|
+
|
622
|
+
# Return a 32-bit integer pixel value given separate red, green, blue, and alpha values.
|
623
|
+
#
|
624
|
+
# Each r, g, b, a value is an integer between 0 and 255.
|
625
|
+
def color_from_rgba(r, g, b, a)
|
626
|
+
# Pack 8-bit-per-channel values
|
627
|
+
return ((a.to_i & 0xff) << 24) |
|
628
|
+
((r.to_i & 0xff) << 16) |
|
629
|
+
((g.to_i & 0xff) << 8) |
|
630
|
+
(b.to_i & 0xff)
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
end # Targa
|
635
|
+
|
636
|
+
# vim:set ts=2 sw=2 sts=2 expandtab:
|