Class: ZPNG::Image

Inherits:
Object
  • Object
show all
Includes:
DeepCopyable
Defined in:
lib/zpng/image.rb

Constant Summary collapse

PNG_HDR =
"\x89PNG\x0d\x0a\x1a\x0a"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DeepCopyable

#deep_copy

Constructor Details

#initialize(x) ⇒ Image

Returns a new instance of Image.



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/zpng/image.rb', line 10

def initialize x
  @chunks = []
  case x
    when IO
      _from_string x.read
    when String
      _from_string x
    when Hash
      _from_hash x
    else
      raise "unsupported input data type #{x.class}"
  end
  if palette && hdr && hdr.depth
    palette.max_colors = 2**hdr.depth
  end
end

Instance Attribute Details

#chunksObject

Returns the value of attribute chunks.



3
4
5
# File 'lib/zpng/image.rb', line 3

def chunks
  @chunks
end

#dataObject

Returns the value of attribute data.



3
4
5
# File 'lib/zpng/image.rb', line 3

def data
  @data
end

#headerObject Also known as: hdr

Returns the value of attribute header.



3
4
5
# File 'lib/zpng/image.rb', line 3

def header
  @header
end

#imagedataObject

Returns the value of attribute imagedata.



3
4
5
# File 'lib/zpng/image.rb', line 3

def imagedata
  @imagedata
end

#scanlinesObject

Returns the value of attribute scanlines.



3
4
5
# File 'lib/zpng/image.rb', line 3

def scanlines
  @scanlines
end

Class Method Details

.load(fname) ⇒ Object Also known as: load_file, from_file

load image from file



33
34
35
36
37
# File 'lib/zpng/image.rb', line 33

def load fname
  open(fname,"rb") do |f|
    self.new(f)
  end
end

Instance Method Details

#==(other_image) ⇒ Object



323
324
325
326
327
# File 'lib/zpng/image.rb', line 323

def == other_image
  width  == other_image.width &&
  height == other_image.height &&
  pixels == other_image.pixels
end

#[](x, y) ⇒ Object



202
203
204
205
# File 'lib/zpng/image.rb', line 202

def [] x, y
  x,y = adam7.convert_coords(x,y) if interlaced?
  scanlines[y][x]
end

#[]=(x, y, newpixel) ⇒ Object



207
208
209
210
211
# File 'lib/zpng/image.rb', line 207

def []= x, y, newpixel
  decode_all_scanlines
  x,y = adam7.convert_coords(x,y) if interlaced?
  scanlines[y][x] = newpixel
end

#_alpha_color(color) ⇒ Object

internal helper method for color types 0 (grayscale) and 2 (truecolor)



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/zpng/image.rb', line 115

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 "color2alpha only intended for GRAYSCALE & RGB color modes"
    end

  color == @alpha_color ? 0 : (2**hdr.depth-1)
end

#adam7Object



27
28
29
# File 'lib/zpng/image.rb', line 27

def adam7
  @adam7 ||= Adam7Decoder.new(self)
end

#alpha_used?Boolean

Returns:

  • (Boolean)


185
186
187
# File 'lib/zpng/image.rb', line 185

def alpha_used?
  @header && @header.alpha_used?
end

#bppObject

image attributes



165
166
167
# File 'lib/zpng/image.rb', line 165

def bpp
  @header && @header.bpp
end

#crop(params) ⇒ Object

returns new image



313
314
315
316
317
# File 'lib/zpng/image.rb', line 313

def crop params
  decode_all_scanlines
  # deep copy first, then crop!
  deep_copy.crop!(params)
end

#crop!(params) ⇒ Object

modifies this image



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/zpng/image.rb', line 285

def crop! params
  decode_all_scanlines

  x,y,h,w = (params[:x]||0), (params[:y]||0), params[:height], params[:width]
  raise "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 "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_scanlinesObject

we must decode all scanlines before doing any modifications or scanlines decoded AFTER modification of UPPER ones will be decoded wrong



215
216
217
218
219
# File 'lib/zpng/image.rb', line 215

def decode_all_scanlines
  return if @all_scanlines_decoded || new_image?
  @all_scanlines_decoded = true
  scanlines.each(&:decode!)
end

#deinterlaceObject

returns new deinterlaced image if deinterlaced OR returns self if no need to deinterlace



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/zpng/image.rb', line 339

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



254
255
256
257
258
259
260
261
# File 'lib/zpng/image.rb', line 254

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



329
330
331
332
333
334
335
# File 'lib/zpng/image.rb', line 329

def each_pixel &block
  height.times do |y|
    width.times do |x|
      yield(self[x,y], x, y)
    end
  end
end

#exportObject



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/zpng/image.rb', line 263

def export
  # fill @imagedata, if not already filled
  imagedata unless new_image?

  # delete old IDAT chunks
  @chunks.delete_if{ |c| c.is_a?(Chunk::IDAT) }

  # fill first_idat @data with compressed imagedata
  @chunks << Chunk::IDAT.new(
    :data => Zlib::Deflate.deflate(scanlines.map(&:export).join, 9)
  )

  # 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



246
247
248
249
250
251
252
# File 'lib/zpng/image.rb', line 246

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

Returns:

  • (Boolean)


177
178
179
# File 'lib/zpng/image.rb', line 177

def grayscale?
  @header && @header.grayscale?
end

#heightObject



173
174
175
# File 'lib/zpng/image.rb', line 173

def height
  @header && @header.height
end

#interlaced?Boolean

Returns:

  • (Boolean)


181
182
183
# File 'lib/zpng/image.rb', line 181

def interlaced?
  @header && @header.interlace != 0
end

#metadataObject



198
199
200
# File 'lib/zpng/image.rb', line 198

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?

Returns:

  • (Boolean)


49
50
51
# File 'lib/zpng/image.rb', line 49

def new_image?
  @new_image
end

#pixelsObject



319
320
321
# File 'lib/zpng/image.rb', line 319

def pixels
  Pixels.new(self)
end

#plteObject Also known as: palette



157
158
159
# File 'lib/zpng/image.rb', line 157

def plte
  @plte ||= @chunks.find{ |c| c.is_a?(Chunk::PLTE) }
end

#save(fname) ⇒ Object

save image to file



43
44
45
# File 'lib/zpng/image.rb', line 43

def save fname
  File.open(fname,"wb"){ |f| f << export }
end

#to_ascii(*args) ⇒ Object



234
235
236
237
238
239
240
241
242
243
244
# File 'lib/zpng/image.rb', line 234

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

#trnsObject

chunks access



152
153
154
155
# File 'lib/zpng/image.rb', line 152

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

#widthObject



169
170
171
# File 'lib/zpng/image.rb', line 169

def width
  @header && @header.width
end