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(options = {}) ⇒ 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, options = {}) ⇒ 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
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/zpng/image.rb', line 23 def initialize x, h={} @chunks = [] @extradata = [] @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
70 71 72 73 74 |
# File 'lib/zpng/image.rb', line 70 def load fname, h={} open(fname,"rb") do |f| self.new(f,h) end end |
Instance Method Details
#==(other_image) ⇒ Object
500 501 502 503 504 505 506 507 508 |
# File 'lib/zpng/image.rb', line 500 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
366 367 368 369 370 |
# File 'lib/zpng/image.rb', line 366 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
372 373 374 375 376 377 |
# File 'lib/zpng/image.rb', line 372 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)
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/zpng/image.rb', line 179 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
64 65 66 |
# File 'lib/zpng/image.rb', line 64 def adam7 @adam7 ||= Adam7Decoder.new(width, height, bpp) end |
#alpha_used? ⇒ Boolean
255 256 257 |
# File 'lib/zpng/image.rb', line 255 def alpha_used? ihdr && @ihdr.alpha_used? end |
#bpp ⇒ Object
image attributes
235 236 237 |
# File 'lib/zpng/image.rb', line 235 def bpp ihdr && @ihdr.bpp end |
#crop(params) ⇒ Object
returns new image
490 491 492 493 494 |
# File 'lib/zpng/image.rb', line 490 def crop params decode_all_scanlines # deep copy first, then crop! deep_copy.crop!(params) end |
#crop!(params) ⇒ Object
modifies this image
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 |
# File 'lib/zpng/image.rb', line 462 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
381 382 383 384 385 |
# File 'lib/zpng/image.rb', line 381 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
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 |
# File 'lib/zpng/image.rb', line 523 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
420 421 422 423 424 425 426 427 |
# File 'lib/zpng/image.rb', line 420 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
510 511 512 513 514 515 516 517 518 519 |
# File 'lib/zpng/image.rb', line 510 def each_pixel &block e = Enumerator.new do |ee| height.times do |y| width.times do |x| ee.yield(self[x,y], x, y) end end end block_given? ? e.each(&block) : e end |
#export(options = {}) ⇒ Object
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 |
# File 'lib/zpng/image.rb', line 429 def export = {} # allow :zlib_level => nil [:zlib_level] = 9 unless .key?(:zlib_level) if .fetch(:repack, true) data = Zlib::Deflate.deflate(scanlines.map(&:export).join, [:zlib_level]) idats = @chunks.find_all{ |c| c.is_a?(Chunk::IDAT) } case idats.size when 0 # add new IDAT @chunks << Chunk::IDAT.new( :data => data ) when 1 idats[0].data = data else idats[0].data = data # delete other IDAT chunks @chunks -= idats[1..-1] end end unless @chunks.last.is_a?(Chunk::IEND) # delete old IEND chunk(s) b/c IEND must be the last one @chunks.delete_if{ |c| c.is_a?(Chunk::IEND) } # add fresh new IEND @chunks << Chunk::IEND.new end PNG_HDR + @chunks.map(&:export).join end |
#extract_block(x, y = nil, w = nil, h = nil) ⇒ Object
412 413 414 415 416 417 418 |
# File 'lib/zpng/image.rb', line 412 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
247 248 249 |
# File 'lib/zpng/image.rb', line 247 def grayscale? ihdr && @ihdr.grayscale? end |
#height ⇒ Object
243 244 245 |
# File 'lib/zpng/image.rb', line 243 def height ihdr && @ihdr.height end |
#ihdr ⇒ Object Also known as: header, hdr
chunks access
216 217 218 |
# File 'lib/zpng/image.rb', line 216 def ihdr @ihdr ||= @chunks.find{ |c| c.is_a?(Chunk::IHDR) } end |
#imagedata_size ⇒ Object
329 330 331 332 333 334 335 |
# File 'lib/zpng/image.rb', line 329 def imagedata_size if new_image? @scanlines.map(&:size).inject(&:+) else imagedata&.size end end |
#inspect ⇒ Object
50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/zpng/image.rb', line 50 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
251 252 253 |
# File 'lib/zpng/image.rb', line 251 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
362 363 364 |
# File 'lib/zpng/image.rb', line 362 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?
86 87 88 |
# File 'lib/zpng/image.rb', line 86 def new_image? @new_image end |
#plte ⇒ Object Also known as: palette
227 228 229 |
# File 'lib/zpng/image.rb', line 227 def plte @plte ||= @chunks.find{ |c| c.is_a?(Chunk::PLTE) } end |
#save(fname, options = {}) ⇒ Object
save image to file
80 81 82 |
# File 'lib/zpng/image.rb', line 80 def save fname, ={} File.open(fname,"wb"){ |f| f << export() } end |
#to_ascii(*args) ⇒ Object
400 401 402 403 404 405 406 407 408 409 410 |
# File 'lib/zpng/image.rb', line 400 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
222 223 224 225 |
# File 'lib/zpng/image.rb', line 222 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
239 240 241 |
# File 'lib/zpng/image.rb', line 239 def width ihdr && @ihdr.width end |