Class: ZPNG::Image
- Inherits:
-
Object
- Object
- ZPNG::Image
- Includes:
- BMP::Reader, DeepCopyable
- Defined in:
- lib/zpng/image.rb
Constant Summary collapse
- PNG_HDR =
"\x89PNG\x0d\x0a\x1a\x0a".force_encoding('binary')
- BMP_HDR =
"BM".force_encoding('binary')
Instance Attribute Summary collapse
-
#chunks ⇒ Object
Returns the value of attribute chunks.
-
#color_class ⇒ Object
now only for (limited) BMP support.
-
#extradata ⇒ Object
Returns the value of attribute extradata.
-
#format ⇒ Object
Returns the value of attribute format.
-
#imagedata ⇒ Object
Returns the value of attribute imagedata.
-
#scanlines ⇒ Object
Returns the value of attribute scanlines.
-
#verbose ⇒ Object
Returns the value of attribute verbose.
Class Method Summary collapse
-
.load(fname, h = {}) ⇒ Object
(also: load_file, from_file)
load image from file.
Instance Method Summary collapse
- #==(other_image) ⇒ Object
- #[](x, y) ⇒ Object
- #[]=(x, y, newcolor) ⇒ Object
-
#_alpha_color(color) ⇒ Object
internal helper method for color types 0 (grayscale) and 2 (truecolor).
- #adam7 ⇒ Object
- #alpha_used? ⇒ Boolean
-
#bpp ⇒ Object
image attributes.
-
#crop(params) ⇒ Object
returns new image.
-
#crop!(params) ⇒ Object
modifies this image.
-
#decode_all_scanlines ⇒ Object
we must decode all scanlines before doing any modifications or scanlines decoded AFTER modification of UPPER ones will be decoded wrong.
-
#deinterlace ⇒ Object
returns new deinterlaced image if deinterlaced OR returns self if no need to deinterlace.
- #each_block(bw, bh, &block) ⇒ Object
- #each_pixel(&block) ⇒ Object
- #export ⇒ Object
- #extract_block(x, y = nil, w = nil, h = nil) ⇒ Object
- #grayscale? ⇒ Boolean
- #height ⇒ Object
-
#ihdr ⇒ Object
(also: #header, #hdr)
chunks access.
- #imagedata_size ⇒ Object
-
#initialize(x, h = {}) ⇒ Image
constructor
possible input params: IO of opened image file String with image file already readed Hash of image parameters to create new blank image.
- #inspect ⇒ Object
- #interlaced? ⇒ Boolean
-
#metadata ⇒ Object
# try to get imagedata size in bytes, w/o storing entire decompressed # stream in memory.
-
#new_image? ⇒ Boolean
(also: #new?)
flag that image is just created, and NOT loaded from file as in Rails’ ActiveRecord::Base#new_record?.
- #pixels ⇒ Object
- #plte ⇒ Object (also: #palette)
-
#save(fname) ⇒ Object
save image to file.
- #to_ascii(*args) ⇒ Object
- #trns ⇒ Object
- #width ⇒ Object
Methods included from BMP::Reader
Methods included from DeepCopyable
Constructor Details
#initialize(x, h = {}) ⇒ Image
possible input params:
IO of opened image file
String with image file already readed
Hash of image parameters to create new blank image
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/zpng/image.rb', line 20 def initialize x, h={} @chunks = [] @color_class = Color @format = :png @verbose = case h[:verbose] when true; 1 when false; 0 else h[:verbose].to_i end case x when IO _from_io x when String _from_io StringIO.new(x) when Hash _from_hash x else raise NotSupported, "unsupported input data type #{x.class}" end if palette && hdr && hdr.depth palette.max_colors = 2**hdr.depth end end |
Instance Attribute Details
#chunks ⇒ Object
Returns the value of attribute chunks.
5 6 7 |
# File 'lib/zpng/image.rb', line 5 def chunks @chunks end |
#color_class ⇒ Object
now only for (limited) BMP support
8 9 10 |
# File 'lib/zpng/image.rb', line 8 def color_class @color_class end |
#extradata ⇒ Object
Returns the value of attribute extradata.
5 6 7 |
# File 'lib/zpng/image.rb', line 5 def extradata @extradata end |
#format ⇒ Object
Returns the value of attribute format.
5 6 7 |
# File 'lib/zpng/image.rb', line 5 def format @format end |
#imagedata ⇒ Object
Returns the value of attribute imagedata.
5 6 7 |
# File 'lib/zpng/image.rb', line 5 def imagedata @imagedata end |
#scanlines ⇒ Object
Returns the value of attribute scanlines.
5 6 7 |
# File 'lib/zpng/image.rb', line 5 def scanlines @scanlines end |
#verbose ⇒ Object
Returns the value of attribute verbose.
5 6 7 |
# File 'lib/zpng/image.rb', line 5 def verbose @verbose end |
Class Method Details
.load(fname, h = {}) ⇒ Object Also known as: load_file, from_file
load image from file
66 67 68 69 70 |
# File 'lib/zpng/image.rb', line 66 def load fname, h={} open(fname,"rb") do |f| self.new(f,h) end end |
Instance Method Details
#==(other_image) ⇒ Object
425 426 427 428 429 430 431 432 433 |
# File 'lib/zpng/image.rb', line 425 def == other_image return false unless other_image.is_a?(Image) return false if width != other_image.width return false if height != other_image.height each_pixel do |c,x,y| return false if c != other_image[x,y] end true end |
#[](x, y) ⇒ Object
302 303 304 305 306 |
# File 'lib/zpng/image.rb', line 302 def [] x, y # extracting this check into a module => +1-2% speed x,y = adam7.convert_coords(x,y) if interlaced? scanlines[y][x] end |
#[]=(x, y, newcolor) ⇒ Object
308 309 310 311 312 313 |
# File 'lib/zpng/image.rb', line 308 def []= x, y, newcolor # extracting these checks into a module => +1-2% speed decode_all_scanlines x,y = adam7.convert_coords(x,y) if interlaced? scanlines[y][x] = newcolor end |
#_alpha_color(color) ⇒ Object
internal helper method for color types 0 (grayscale) and 2 (truecolor)
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/zpng/image.rb', line 143 def _alpha_color color return nil unless trns # For color type 0 (grayscale), the tRNS chunk contains a single gray level value, stored in the format: # # Gray: 2 bytes, range 0 .. (2^bitdepth)-1 # # For color type 2 (truecolor), the tRNS chunk contains a single RGB color value, stored in the format: # # Red: 2 bytes, range 0 .. (2^bitdepth)-1 # Green: 2 bytes, range 0 .. (2^bitdepth)-1 # Blue: 2 bytes, range 0 .. (2^bitdepth)-1 # # (If the image bit depth is less than 16, the least significant bits are used and the others are 0) # Pixels of the specified gray level are to be treated as transparent (equivalent to alpha value 0); # all other pixels are to be treated as fully opaque ( alpha = (2^bitdepth)-1 ) @alpha_color ||= case hdr.color when COLOR_GRAYSCALE v = trns.data.unpack('n')[0] & (2**hdr.depth-1) Color.from_grayscale(v, :depth => hdr.depth) when COLOR_RGB a = trns.data.unpack('n3').map{ |v| v & (2**hdr.depth-1) } Color.new(*a, :depth => hdr.depth) else raise Exception, "color2alpha only intended for GRAYSCALE & RGB color modes" end color == @alpha_color ? 0 : (2**hdr.depth-1) end |
#adam7 ⇒ Object
60 61 62 |
# File 'lib/zpng/image.rb', line 60 def adam7 @adam7 ||= Adam7Decoder.new(self) end |
#alpha_used? ⇒ Boolean
219 220 221 |
# File 'lib/zpng/image.rb', line 219 def alpha_used? ihdr && @ihdr.alpha_used? end |
#bpp ⇒ Object
image attributes
199 200 201 |
# File 'lib/zpng/image.rb', line 199 def bpp ihdr && @ihdr.bpp end |
#crop(params) ⇒ Object
returns new image
415 416 417 418 419 |
# File 'lib/zpng/image.rb', line 415 def crop params decode_all_scanlines # deep copy first, then crop! deep_copy.crop!(params) end |
#crop!(params) ⇒ Object
modifies this image
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 |
# File 'lib/zpng/image.rb', line 387 def crop! params decode_all_scanlines x,y,h,w = (params[:x]||0), (params[:y]||0), params[:height], params[:width] raise ArgumentError, "negative params not allowed" if [x,y,h,w].any?{ |x| x < 0 } # adjust crop sizes if they greater than image sizes h = self.height-y if (y+h) > self.height w = self.width-x if (x+w) > self.width raise ArgumentError, "negative params not allowed (p2)" if [x,y,h,w].any?{ |x| x < 0 } # delete excess scanlines at tail scanlines[(y+h)..-1] = [] if (y+h) < scanlines.size # delete excess scanlines at head scanlines[0,y] = [] if y > 0 # crop remaining scanlines scanlines.each{ |l| l.crop!(x,w) } # modify header hdr.height, hdr.width = h, w # return self self end |
#decode_all_scanlines ⇒ Object
we must decode all scanlines before doing any modifications or scanlines decoded AFTER modification of UPPER ones will be decoded wrong
317 318 319 320 321 |
# File 'lib/zpng/image.rb', line 317 def decode_all_scanlines return if @all_scanlines_decoded || new_image? @all_scanlines_decoded = true scanlines.each(&:decode!) end |
#deinterlace ⇒ Object
returns new deinterlaced image if deinterlaced OR returns self if no need to deinterlace
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 |
# File 'lib/zpng/image.rb', line 445 def deinterlace return self unless interlaced? # copy all but 'interlace' header params h = Hash[*%w'width height depth color compression filter'.map{ |k| [k.to_sym, hdr.send(k)] }.flatten] # don't auto-add palette chunk h[:palette] = nil # create new img new_img = self.class.new h # copy all but hdr/imagedata/end chunks chunks.each do |chunk| next if chunk.is_a?(Chunk::IHDR) next if chunk.is_a?(Chunk::IDAT) next if chunk.is_a?(Chunk::IEND) new_img.chunks << chunk.deep_copy end # pixel-by-pixel copy each_pixel do |c,x,y| new_img[x,y] = c end new_img end |
#each_block(bw, bh, &block) ⇒ Object
356 357 358 359 360 361 362 363 |
# File 'lib/zpng/image.rb', line 356 def each_block bw,bh, &block 0.upto(height/bh-1) do |by| 0.upto(width/bw-1) do |bx| b = extract_block(bx*bw, by*bh, bw, bh) yield b end end end |
#each_pixel(&block) ⇒ Object
435 436 437 438 439 440 441 |
# File 'lib/zpng/image.rb', line 435 def each_pixel &block height.times do |y| width.times do |x| yield(self[x,y], x, y) end end end |
#export ⇒ Object
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 |
# File 'lib/zpng/image.rb', line 365 def export # XXX creating new IDAT must be BEFORE deleting old IDAT chunks idat = Chunk::IDAT.new( :data => Zlib::Deflate.deflate(scanlines.map(&:export).join, 9) ) # delete old IDAT chunks @chunks.delete_if{ |c| c.is_a?(Chunk::IDAT) } # add newly created IDAT @chunks << idat # delete IEND chunk(s) b/c we just added a new chunk and IEND must be the last one @chunks.delete_if{ |c| c.is_a?(Chunk::IEND) } # add fresh new IEND @chunks << Chunk::IEND.new PNG_HDR + @chunks.map(&:export).join end |
#extract_block(x, y = nil, w = nil, h = nil) ⇒ Object
348 349 350 351 352 353 354 |
# File 'lib/zpng/image.rb', line 348 def extract_block x,y=nil,w=nil,h=nil if x.is_a?(Hash) Block.new(self,x[:x], x[:y], x[:width], x[:height]) else Block.new(self,x,y,w,h) end end |
#grayscale? ⇒ Boolean
211 212 213 |
# File 'lib/zpng/image.rb', line 211 def grayscale? ihdr && @ihdr.grayscale? end |
#height ⇒ Object
207 208 209 |
# File 'lib/zpng/image.rb', line 207 def height ihdr && @ihdr.height end |
#ihdr ⇒ Object Also known as: header, hdr
chunks access
180 181 182 |
# File 'lib/zpng/image.rb', line 180 def ihdr @ihdr ||= @chunks.find{ |c| c.is_a?(Chunk::IHDR) } end |
#imagedata_size ⇒ Object
250 251 252 253 254 255 256 |
# File 'lib/zpng/image.rb', line 250 def imagedata_size if new_image? @scanlines.map(&:size).inject(&:+) else imagedata.size end end |
#inspect ⇒ Object
46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/zpng/image.rb', line 46 def inspect "#<ZPNG::Image " + %w'width height bpp chunks scanlines'.map do |k| v = case (v = send(k)) when Array "[#{v.size} entries]" when String v.size > 40 ? "[#{v.bytesize} bytes]" : v.inspect else v.inspect end "#{k}=#{v}" end.compact.join(", ") + ">" end |
#interlaced? ⇒ Boolean
215 216 217 |
# File 'lib/zpng/image.rb', line 215 def interlaced? ihdr && @ihdr.interlace != 0 end |
#metadata ⇒ Object
# try to get imagedata size in bytes, w/o storing entire decompressed
# stream in memory. used in bin/zpng
# result: less memory used on big images, but speed gain near 1-2% in best case,
# and 2x slower in worst case because imagedata decoded 2 times
def imagedata_size
if @imagedata
# already decompressed
@imagedata.size
else
zi = nil
@imagedata_size ||=
begin
zi = Zlib::Inflate.new(Zlib::MAX_WBITS)
io = StringIO.new(_imagedata)
while !io.eof? && !zi.finished?
n = zi.inflate(io.read(16384))
end
zi.finish unless zi.finished?
zi.total_out
ensure
zi.close if zi && !zi.closed?
end
end
end
298 299 300 |
# File 'lib/zpng/image.rb', line 298 def @metadata ||= Metadata.new(self) end |
#new_image? ⇒ Boolean Also known as: new?
flag that image is just created, and NOT loaded from file as in Rails’ ActiveRecord::Base#new_record?
82 83 84 |
# File 'lib/zpng/image.rb', line 82 def new_image? @new_image end |
#plte ⇒ Object Also known as: palette
191 192 193 |
# File 'lib/zpng/image.rb', line 191 def plte @plte ||= @chunks.find{ |c| c.is_a?(Chunk::PLTE) } end |
#save(fname) ⇒ Object
save image to file
76 77 78 |
# File 'lib/zpng/image.rb', line 76 def save fname File.open(fname,"wb"){ |f| f << export } end |
#to_ascii(*args) ⇒ Object
336 337 338 339 340 341 342 343 344 345 346 |
# File 'lib/zpng/image.rb', line 336 def to_ascii *args if scanlines.any? if interlaced? height.times.map{ |y| width.times.map{ |x| self[x,y].to_ascii(*args) }.join }.join("\n") else scanlines.map{ |l| l.to_ascii(*args) }.join("\n") end else super() end end |
#trns ⇒ Object
186 187 188 189 |
# File 'lib/zpng/image.rb', line 186 def trns # not used "@trns ||= ..." here b/c it will call find() each time of there's no TRNS chunk defined?(@trns) ? @trns : (@trns=@chunks.find{ |c| c.is_a?(Chunk::TRNS) }) end |
#width ⇒ Object
203 204 205 |
# File 'lib/zpng/image.rb', line 203 def width ihdr && @ihdr.width end |