Skip to content

Commit

Permalink
Updated documents
Browse files Browse the repository at this point in the history
  • Loading branch information
ucnv committed Nov 6, 2014
1 parent 10b46b0 commit 34c33a4
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 29 deletions.
182 changes: 158 additions & 24 deletions lib/pnglitch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,156 @@
#
# Also it provides options to generate varied glitch results.
#
# = Synopsis
# = Usage
#
# p = PNGlitch.open '/path/to/your/image.png'
# p.glitch do |data|
# == Simple glitch
#
# png = PNGlitch.open '/path/to/your/image.png'
# png.glitch do |data|
# data.gsub /\d/, 'x'
# end
# p.save '/path/to/broken/image.png'
# p.close
# png.save '/path/to/broken/image.png'
# png.close
#
# The code above can be written with a block like below:
#
# PNGlitch.open('/path/to/your/image.png') do |p|
# p.glitch do |data|
# PNGlitch.open('/path/to/your/image.png') do |png|
# png.glitch do |data|
# data.gsub /\d/, 'x'
# end
# png.save '/path/to/broken/image.png'
# end
#
# The +glitch+ method treats the decompressed data into one String instance. It will be
# very convenient, but please note that it could take a huge size of memory. For example,
# a normal PNG image in 3264 x 2448 pixels makes over 23 MB of decompressed data.
# In case that the memory usage becomes a concern, it can be written to use IO instead of
# String.
#
# PNGlitch.open('/path/to/your/image.png') do |png|
# buf = 2 ** 18
# png.glitch_as_io do |io|
# until io.eof? do
# d = io.read(buf)
# io.pos -= d.size
# io.print(d.gsub(/\d/, 'x'))
# end
# end
# png.save '/path/to/broken/image.png'
# end
#
# PNGlitch also provides to manipulate with each scanline.
#
# PNGlitch.open('/path/to/your/image.png') do |png|
# png.each_scanline do |scanline|
# scanline.gsub! /\d/, 'x'
# end
# png.save '/path/to/broken/image.png'
# end
#
# Depe:nding a viewer application, the result of the first example using +glitch+ can
# detected as unopenable file, because of breaking the filter type bytes (Most applications
# will ignore it, but I found the library in java.awt get failed). The operation with
# +each_scanline+ will be more careful for memory usage and the file itself.
# It is a polite way, but is slower than the rude +glitch+.
#
#
# == Scanlines and filter types
#
# Scanline consists of data of pixels and a filter type value.
#
# To change the data of pixels, use +Scanline#replace_data+.
#
# png.each_scanline do |scanline|
# data = scanline.data
# scanline.replace_data(data.gsub(/\d/, 'x'))
# end
#
# Or +Scanline#gsub!+ works like +String#gsub!+
#
# png.each_scanline do |scanline|
# scanline.gsub! /\d/, 'x'
# end
#
# Filter is a tiny function for optimizing PNG compression. It can be set different types
# with each scanline. The five filter types are defined in the spec, are named +None+, +Up+,
# +Sub+, +Average+ and +Paeth+ (+None+ means no filter, this filter type makes "raw" data).
# Internally five digits (0-4) become the references.
#
# The filter types must be the most important factor behind the representation of glitch
# results. Each filter has different effect.
#
# Generally in PNG file, scanlines has a variety of filter types on each. As in a
# convertion by image processing applications (like Photoshop or ImageMagick) they try to
# apply proper filter types with each scanlines.
#
# You can check the values like:
#
# puts png.filter_types
#
# With +each_scanline+, we can reach the filters particularly.
#
# png.each_scanline do |scanline|
# scanline.change_filter 3
# end
#
# The example above puts all filter types in 3 (type +Average+). +change_filter+ will
# apply new filter type values correctly. It computes filters and makes the PNG well
# formatted, and any glitch won't get happened. It also means the output image would have
# completely looks the same as the input one.
#
# However glitches might reveal the difference of filter types.
#
# PNGlitch.open(infile) do |png|
# png.each_scanline do |scanline|
# scanline.change_filter 3
# end
# png.glitch do |data|
# data.gsub /\d/, 'x'
# end
# png.save outfile1
# end
#
# PNGlitch.open(infile) do |png|
# png.each_scanline do |scanline|
# scanline.change_filter 4
# end
# png.glitch do |data|
# data.gsub /\d/, 'x'
# end
# p.save '/path/to/broken/image.png'
# png.save outfile2
# end
#
# With the results of the example above, obviously we can recognize the filter types make
# a big difference. The filter is distinct and interesting thing in PNG glitching.
# To put all filter type in a same value before glitching, we could see the signature
# taste of each filter type. (Note that +change_filter+ will be a little bit slow, image
# processing libraries like ImageMagick also have the option to put all filter type in
# same ones and they will process faster.)
#
# This library provides a simple method to change the filter type so that generating all
# possible effects in PNG glitch.
#
# PNGlitch also provides to make the filter types wrong. Following example swaps the
# filter types but remains data unchanged. It means to put a wrong filter type applied.
#
# png.each_scanline do |scanline|
# scanline.graft rand(4)
# end
#
# Additionally, it is possible to break the filter function. Registering a (wrong) filter
# function like below, we can make glitches with an algorithmic touch.
#
# png.each_scanline do |scanline|
# scanline.register_filter_encoder do |data, prev|
# d = data.dup
# d.size.times.reverse_each do |i|
# x = d.getbyte(i)
# v = prev ? prev.getbyte((i - 5).abs) : 0
# d.setbyte(i, (x - v) & 0xff)
# end
# d
# end
# end
#
# == States
Expand All @@ -43,24 +177,23 @@
# | Raw data | -> | Filtered data | -> | Compressed data | -> | Formatted file |
# +----------+ +---------------+ +-----------------+ +----------------+
#
# It shows that there are two states between raw data and a result file, and it means there are
# two states possible to glitch. This library provides to choose of the state to glitch.
# It shows that there are two states between raw data and a result file, and it means there
# are two states possible to glitch. This library provides to choose of the state to glitch.
#
# == Scanlines and filters
# All examples cited thus far are operations to "filtered data". On the other hand, PNGlitch
# can touch the "compressed data" through +glitch_after_compress+ method:
#
# The five filter types are defined in the spec for optimizing PNG compression. It must be
# the most important factor behind the representation of glitch results. Each filter has
# different effect.
# In this library we can select the filter type so that generating all possible effects
# in PNG glitch.
# png.glitch_after_compress do |data|
# data[rand(data.size)] = 'x'
# data
# end
#
# Glitch against the compressed data makes slightly different pictures from other results.
# But sometimes this scratch would break the compression and make the file unopenable.
#
module PNGlitch
VERSION = '0.0.0'

DEFAULT_LIMIT_OF_DECOMPRESSED_FILE_SIZE = 16 * 1024 ** 3
DEFAULT_LIMIT_OF_MEMORY_USAGE = 4 * 1024 ** 3

class << self

#
Expand All @@ -85,11 +218,12 @@ class << self
# end
#
# Under normal conditions, the size of the decompressed data of PNG image becomes
# (1 + image_width * sample_size) * image_height in bytes (for example, an image in
# 1920x1080 pixels might make about 8.29 MB of decompressed data). To avoid the attack
# known as "zip bomb", PNGlitch will throw an error when decompressed data goes over
# twice the expected size. If it's sure that the passed file is safe, the upper limit
# of decompressed data size can be set in +open+'s option. Like:
# <tt>(1 + image_width * sample_size) * image_height</tt> in bytes (mostly over 4 times
# the amount of pixels).
# To avoid the attack known as "zip bomb", PNGlitch will throw an error when
# decompressed data goes over twice the expected size. If it's sure that the passed
# file is safe, the upper limit of decompressed data size can be set in +open+'s option.
# Like:
#
# PNGlitch.open(infile, limit_of_decompressed_data_size: 1 * 1024 ** 3)
#
Expand Down
22 changes: 20 additions & 2 deletions lib/pnglitch/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,7 @@ def filter_types
# To be polite to the filter types, use +each_scanline+ instead.
#
# Since this method sets the decompressed data into String, it may use a massive amount of
# memory. It will raise an error when the data goes over the limit.
# In such case, please treat the data as IO through +glitch_as_io+ instead.
# memory. To decrease the memory usage, treat the data as IO through +glitch_as_io+ instead.
#
def glitch &block # :yield: data
warn_if_compressed_data_modified
Expand Down Expand Up @@ -292,6 +291,25 @@ def compress(
#
# This method is safer than +glitch+ but will be a little bit slow.
#
# -----
#
# Please note that +each_scanline+ will apply the filters after the loop. It means
# a following example doesn't work as expected.
#
# pnglicth.each_scanline do |line|
# line.change_filter 3
# line.gsub! /\d/, 'x' # wants to glitch after changing filters.
# end
#
# To glitch after applying the new filter types, it should be called separately like:
#
# pnglicth.each_scanline do |line|
# line.change_filter 3
# end
# pnglicth.each_scanline do |line|
# line.gsub! /\d/, 'x'
# end
#
def each_scanline # :yield: scanline
return enum_for :each_scanline unless block_given?

Expand Down
1 change: 1 addition & 0 deletions lib/pnglitch/filter.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module PNGlitch

# Filter represents the filtering functions that is defined in PNG spec.
#
class Filter

NONE = 0
Expand Down
6 changes: 3 additions & 3 deletions lib/pnglitch/scanline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module PNGlitch

# Scanline is the class that represents a particular PNG image scanline.
#
# It is constructed by a filter type and a filtered pixel data.
# It consists of a filter type and a filtered pixel data.
#
class Scanline

Expand Down Expand Up @@ -48,7 +48,7 @@ def data
#
# Replaces data with given Regexp +pattern+ and +replacement+.
#
# It is same as +scanline.replace_data(scanline.data.gsub(pattern, replacement))+.
# It is same as <tt>scanline.replace_data(scanline.data.gsub(pattern, replacement))</tt>.
# When the data size has changed, the data will be chopped or padded with null string
# in original size.
#
Expand Down Expand Up @@ -105,7 +105,7 @@ def register_filter_encoder encoder = nil, &block
end

#
# Registers a custom filter function to decode data with a Proc object or a block.
# Registers a custom filter function to decode data.
#
# With this operation, it will be able to change filter decoding behavior despite
# the specified filter type value. It takes a Proc object or a block.
Expand Down

0 comments on commit 34c33a4

Please sign in to comment.