pnglitch 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/LICENSE.txt +1 -1
- data/bin/pnglitch +1 -3
- data/lib/pnglitch/base.rb +152 -67
- data/lib/pnglitch/filter.rb +75 -81
- data/lib/pnglitch/scanline.rb +4 -7
- data/lib/pnglitch.rb +29 -28
- data/pnglitch.gemspec +2 -2
- data/spec/fixtures/ina.png +0 -0
- data/spec/fixtures/{bomb.png → inb.png} +0 -0
- data/spec/fixtures/inc.png +0 -0
- data/spec/pnglitch_filter_spec.rb +6 -0
- data/spec/pnglitch_spec.rb +180 -29
- metadata +25 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44d05884a52a43971ee2d6e6f0c0dd79450f7435
|
4
|
+
data.tar.gz: 07e160fdf0a363ac043b94c52e76d9446d7ac66f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73efdd4e1fb833a268a12b35b39359db9225f0c5a1f0c332ae2b35b3559d317210498fd387f5c1f89fe5ad71105bbd77bc77d173faf0db6e974da488720b43f1
|
7
|
+
data.tar.gz: 4f413a38b0e60ac3362ebeea3b0a0a27860fe985084d4dcff6a67e8f446e49536b979b289a6241f3351c3d424f5e471f140a41240d21429f7210e8a247db6a2f
|
data/.travis.yml
CHANGED
data/LICENSE.txt
CHANGED
data/bin/pnglitch
CHANGED
data/lib/pnglitch/base.rb
CHANGED
@@ -7,7 +7,7 @@ module PNGlitch
|
|
7
7
|
class Base
|
8
8
|
|
9
9
|
attr_reader :width, :height, :sample_size, :is_compressed_data_modified
|
10
|
-
attr_accessor :head_data, :tail_data, :compressed_data, :filtered_data
|
10
|
+
attr_accessor :head_data, :tail_data, :compressed_data, :filtered_data, :idat_chunk_size
|
11
11
|
|
12
12
|
#
|
13
13
|
# Instanciate the class with the passed +file+
|
@@ -16,8 +16,8 @@ module PNGlitch
|
|
16
16
|
path = Pathname.new file
|
17
17
|
@head_data = StringIO.new
|
18
18
|
@tail_data = StringIO.new
|
19
|
-
@compressed_data = Tempfile.new 'compressed', :
|
20
|
-
@filtered_data = Tempfile.new 'filtered', :
|
19
|
+
@compressed_data = Tempfile.new 'compressed', encoding: 'ascii-8bit'
|
20
|
+
@filtered_data = Tempfile.new 'filtered', encoding: 'ascii-8bit'
|
21
21
|
@idat_chunk_size = nil
|
22
22
|
|
23
23
|
open(path, 'rb') do |io|
|
@@ -37,6 +37,7 @@ module PNGlitch
|
|
37
37
|
}
|
38
38
|
@width = ihdr[:width]
|
39
39
|
@height = ihdr[:height]
|
40
|
+
@interlace = ihdr[:interlace_method]
|
40
41
|
@sample_size = {0 => 1, 2 => 3, 3 => 1, 4 => 2, 6 => 4}[ihdr[:color_type]]
|
41
42
|
io.pos -= 13
|
42
43
|
end
|
@@ -95,10 +96,10 @@ module PNGlitch
|
|
95
96
|
def filter_types
|
96
97
|
types = []
|
97
98
|
wrap_with_rewind(@filtered_data) do
|
98
|
-
|
99
|
+
scanline_positions.each do |pos|
|
100
|
+
@filtered_data.pos = pos
|
99
101
|
byte = @filtered_data.read 1
|
100
102
|
types << byte.unpack('C').first
|
101
|
-
@filtered_data.pos += @width * @sample_size
|
102
103
|
end
|
103
104
|
end
|
104
105
|
types
|
@@ -156,9 +157,6 @@ module PNGlitch
|
|
156
157
|
#
|
157
158
|
# To set a glitched result, return the modified value in the block.
|
158
159
|
#
|
159
|
-
# It will raise an error when the data goes over the limit. In such case, please treat
|
160
|
-
# the data as IO through +glitch_after_compress_as_io+ instead.
|
161
|
-
#
|
162
160
|
# Once the compressed data is glitched, PNGlitch will warn about modifications to
|
163
161
|
# filtered (decompressed) data because this method does not decompress the glitched
|
164
162
|
# compressed data again. It means that calling +glitch+ after +glitch_after_compress+
|
@@ -189,26 +187,32 @@ module PNGlitch
|
|
189
187
|
end
|
190
188
|
|
191
189
|
#
|
192
|
-
# (Re-)computes the filtering methods
|
193
|
-
#
|
194
|
-
# On each scanline, it will compute them and apply the results as the filtered data.
|
190
|
+
# (Re-)computes the filtering methods on each scanline.
|
195
191
|
#
|
196
192
|
def apply_filters prev_filters = nil, filter_codecs = nil
|
197
193
|
prev_filters = filter_types if prev_filters.nil?
|
198
194
|
filter_codecs = [] if filter_codecs.nil?
|
199
195
|
current_filters = []
|
200
196
|
prev = nil
|
201
|
-
|
197
|
+
line_sizes = []
|
198
|
+
scanline_positions.push(@filtered_data.size).inject do |m, n|
|
199
|
+
line_sizes << n - m - 1
|
200
|
+
n
|
201
|
+
end
|
202
202
|
wrap_with_rewind(@filtered_data) do
|
203
203
|
# decode all scanlines
|
204
204
|
prev_filters.each_with_index do |type, i|
|
205
205
|
byte = @filtered_data.read 1
|
206
206
|
current_filters << byte.unpack('C').first
|
207
|
+
line_size = line_sizes[i]
|
207
208
|
line = @filtered_data.read line_size
|
208
209
|
filter = Filter.new type, @sample_size
|
209
210
|
if filter_codecs[i] && filter_codecs[i][:decoder]
|
210
211
|
filter.decoder = filter_codecs[i][:decoder]
|
211
212
|
end
|
213
|
+
if !prev.nil? && @interlace_pass_count.include?(i + 1) # make sure prev to be nil if interlace pass is changed
|
214
|
+
prev = nil
|
215
|
+
end
|
212
216
|
decoded = filter.decode line, prev
|
213
217
|
@filtered_data.pos -= line_size
|
214
218
|
@filtered_data << decoded
|
@@ -216,24 +220,29 @@ module PNGlitch
|
|
216
220
|
end
|
217
221
|
# encode all
|
218
222
|
filter_codecs.reverse!
|
223
|
+
line_sizes.reverse!
|
219
224
|
data_amount = @filtered_data.pos # should be eof
|
225
|
+
ref = data_amount
|
220
226
|
current_filters.reverse_each.with_index do |type, i|
|
221
|
-
|
222
|
-
|
223
|
-
@filtered_data.pos =
|
227
|
+
line_size = line_sizes[i]
|
228
|
+
ref -= line_size + 1
|
229
|
+
@filtered_data.pos = ref + 1
|
224
230
|
line = @filtered_data.read line_size
|
225
|
-
posb = pos - (1 + line_size) - line_size
|
226
231
|
prev = nil
|
227
|
-
|
228
|
-
@filtered_data.pos =
|
232
|
+
if !line_sizes[i + 1].nil?
|
233
|
+
@filtered_data.pos = ref - line_size
|
229
234
|
prev = @filtered_data.read line_size
|
230
235
|
end
|
236
|
+
# make sure prev to be nil if interlace pass is changed
|
237
|
+
if @interlace_pass_count.include?(current_filters.size - i)
|
238
|
+
prev = nil
|
239
|
+
end
|
231
240
|
filter = Filter.new type, @sample_size
|
232
241
|
if filter_codecs[i] && filter_codecs[i][:encoder]
|
233
242
|
filter.encoder = filter_codecs[i][:encoder]
|
234
243
|
end
|
235
244
|
encoded = filter.encode line, prev
|
236
|
-
@filtered_data.pos =
|
245
|
+
@filtered_data.pos = ref + 1
|
237
246
|
@filtered_data << encoded
|
238
247
|
end
|
239
248
|
end
|
@@ -269,7 +278,7 @@ module PNGlitch
|
|
269
278
|
#
|
270
279
|
# Process each scanlines.
|
271
280
|
#
|
272
|
-
# It takes a block with a parameter. The parameter
|
281
|
+
# It takes a block with a parameter. The parameter must be an instance of
|
273
282
|
# PNGlitch::Scanline and it provides ways to edit the filter type and the data
|
274
283
|
# of the scanlines. Normally it iterates the number of the PNG image height.
|
275
284
|
#
|
@@ -280,7 +289,7 @@ module PNGlitch
|
|
280
289
|
# end
|
281
290
|
#
|
282
291
|
# pnglicth.each_scanline do |line|
|
283
|
-
# line.change_filter 3 # change all filter to 3, data will get re-filtering (it'
|
292
|
+
# line.change_filter 3 # change all filter to 3, data will get re-filtering (it won't be a glitch)
|
284
293
|
# end
|
285
294
|
#
|
286
295
|
# pnglicth.each_scanline do |line|
|
@@ -293,7 +302,7 @@ module PNGlitch
|
|
293
302
|
#
|
294
303
|
# -----
|
295
304
|
#
|
296
|
-
# Please note that +each_scanline+ will apply the filters after the loop. It means
|
305
|
+
# Please note that +each_scanline+ will apply the filters *after* the loop. It means
|
297
306
|
# a following example doesn't work as expected.
|
298
307
|
#
|
299
308
|
# pnglicth.each_scanline do |line|
|
@@ -312,32 +321,24 @@ module PNGlitch
|
|
312
321
|
#
|
313
322
|
def each_scanline # :yield: scanline
|
314
323
|
return enum_for :each_scanline unless block_given?
|
315
|
-
|
316
324
|
prev_filters = self.filter_types
|
317
325
|
is_refilter_needed = false
|
318
326
|
filter_codecs = []
|
319
327
|
wrap_with_rewind(@filtered_data) do
|
320
|
-
pos = 0
|
321
328
|
at = 0
|
322
|
-
|
323
|
-
scanline = Scanline.new @filtered_data, pos,
|
329
|
+
scanline_positions.push(@filtered_data.size).inject do |pos, delimit|
|
330
|
+
scanline = Scanline.new @filtered_data, pos, (delimit - pos - 1), at
|
324
331
|
yield scanline
|
325
|
-
|
326
|
-
is_refilter_needed = true
|
327
|
-
else
|
328
|
-
prev_filters[at] = scanline.filter_type # forget the prev filter when "graft"
|
329
|
-
end
|
330
|
-
filter_codecs << scanline.filter_codec
|
331
|
-
if !filter_codecs.last[:encoder].nil? || !filter_codecs.last[:decoder].nil?
|
332
|
+
if fabricate_scanline(scanline, prev_filters, filter_codecs)
|
332
333
|
is_refilter_needed = true
|
333
334
|
end
|
334
335
|
at += 1
|
335
|
-
|
336
|
-
@filtered_data.pos = pos
|
336
|
+
delimit
|
337
337
|
end
|
338
338
|
end
|
339
339
|
apply_filters(prev_filters, filter_codecs) if is_refilter_needed
|
340
340
|
compress
|
341
|
+
self
|
341
342
|
end
|
342
343
|
|
343
344
|
#
|
@@ -348,36 +349,61 @@ module PNGlitch
|
|
348
349
|
def scanline_at index_or_range
|
349
350
|
base = self
|
350
351
|
prev_filters = self.filter_types
|
351
|
-
filter_codecs =
|
352
|
+
filter_codecs = Array.new(prev_filters.size)
|
352
353
|
scanlines = []
|
353
354
|
index_or_range = self.filter_types.size - 1 if index_or_range == -1
|
354
355
|
range = index_or_range.is_a?(Range) ? index_or_range : [index_or_range]
|
355
|
-
|
356
|
-
|
356
|
+
|
357
|
+
at = 0
|
358
|
+
scanline_positions.push(@filtered_data.size).inject do |pos, delimit|
|
357
359
|
if range.include? at
|
358
|
-
s = Scanline.new(@filtered_data, pos,
|
359
|
-
|
360
|
-
|
361
|
-
is_refilter_needed = true
|
362
|
-
else
|
363
|
-
prev_filters[at] = scanline.filter_type
|
360
|
+
s = Scanline.new(@filtered_data, pos, (delimit - pos - 1), at) do |scanline|
|
361
|
+
if base.fabricate_scanline(scanline, prev_filters, filter_codecs)
|
362
|
+
base.apply_filters(prev_filters, filter_codecs)
|
364
363
|
end
|
365
|
-
codec = scanline.filter_codec
|
366
|
-
if !codec[:encoder].nil? || !codec[:decoder].nil?
|
367
|
-
filter_codecs = Array.new(prev_filters.size) if filter_codecs.nil?
|
368
|
-
filter_codecs[at] = codec
|
369
|
-
is_refilter_needed = true
|
370
|
-
end
|
371
|
-
base.apply_filters(prev_filters, filter_codecs) if is_refilter_needed
|
372
364
|
base.compress
|
373
365
|
end
|
374
366
|
scanlines << s
|
375
367
|
end
|
376
|
-
|
368
|
+
at += 1
|
369
|
+
delimit
|
377
370
|
end
|
378
371
|
scanlines.size <= 1 ? scanlines.first : scanlines
|
379
372
|
end
|
380
373
|
|
374
|
+
def fabricate_scanline scanline, prev_filters, filter_codecs # :nodoc:
|
375
|
+
at = scanline.index
|
376
|
+
is_refilter_needed = false
|
377
|
+
unless scanline.prev_filter_type.nil?
|
378
|
+
is_refilter_needed = true
|
379
|
+
else
|
380
|
+
prev_filters[at] = scanline.filter_type
|
381
|
+
end
|
382
|
+
codec = filter_codecs[at] = scanline.filter_codec
|
383
|
+
if !codec[:encoder].nil? || !codec[:decoder].nil?
|
384
|
+
is_refilter_needed = true
|
385
|
+
end
|
386
|
+
is_refilter_needed
|
387
|
+
end
|
388
|
+
|
389
|
+
#
|
390
|
+
# Changes filter type values to passed +filter_type+ in all scanlines
|
391
|
+
#
|
392
|
+
def change_all_filters filter_type
|
393
|
+
each_scanline do |line|
|
394
|
+
line.change_filter filter_type
|
395
|
+
end
|
396
|
+
compress
|
397
|
+
self
|
398
|
+
end
|
399
|
+
|
400
|
+
#
|
401
|
+
# Checks if it is interlaced.
|
402
|
+
#
|
403
|
+
def interlaced?
|
404
|
+
@interlace == 1
|
405
|
+
end
|
406
|
+
|
381
407
|
#
|
382
408
|
# Rewrites the width value.
|
383
409
|
#
|
@@ -394,6 +420,7 @@ module PNGlitch
|
|
394
420
|
end
|
395
421
|
end
|
396
422
|
@head_data.rewind
|
423
|
+
w
|
397
424
|
end
|
398
425
|
|
399
426
|
#
|
@@ -414,27 +441,27 @@ module PNGlitch
|
|
414
441
|
end
|
415
442
|
end
|
416
443
|
@head_data.rewind
|
444
|
+
h
|
417
445
|
end
|
418
446
|
|
419
447
|
#
|
420
448
|
# Save to the +file+.
|
421
449
|
#
|
422
450
|
def save file
|
423
|
-
@head_data
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
io <<
|
451
|
+
wrap_with_rewind(@head_data, @tail_data, @compressed_data) do
|
452
|
+
open(file, 'w') do |io|
|
453
|
+
io << @head_data.read
|
454
|
+
chunk_size = @idat_chunk_size || @compressed_data.size
|
455
|
+
type = 'IDAT'
|
456
|
+
until @compressed_data.eof? do
|
457
|
+
data = @compressed_data.read(chunk_size)
|
458
|
+
io << [data.size].pack('N')
|
459
|
+
io << type
|
460
|
+
io << data
|
461
|
+
io << [Zlib.crc32(data, Zlib.crc32(type))].pack('N')
|
462
|
+
end
|
463
|
+
io << @tail_data.read
|
436
464
|
end
|
437
|
-
io << @tail_data.read
|
438
465
|
end
|
439
466
|
self
|
440
467
|
end
|
@@ -460,6 +487,64 @@ module PNGlitch
|
|
460
487
|
end
|
461
488
|
end
|
462
489
|
|
490
|
+
# Calculate positions of scanlines
|
491
|
+
def scanline_positions
|
492
|
+
scanline_pos = [0]
|
493
|
+
amount = @filtered_data.size
|
494
|
+
@interlace_pass_count = []
|
495
|
+
if self.interlaced?
|
496
|
+
# Adam7
|
497
|
+
# Pass 1
|
498
|
+
v = 1 + (@width / 8.0).ceil * @sample_size
|
499
|
+
(@height / 8.0).ceil.times do
|
500
|
+
scanline_pos << scanline_pos.last + v
|
501
|
+
end
|
502
|
+
@interlace_pass_count << scanline_pos.size
|
503
|
+
# Pass 2
|
504
|
+
v = 1 + ((@width - 4) / 8.0).ceil * @sample_size
|
505
|
+
(@height / 8.0).ceil.times do
|
506
|
+
scanline_pos << scanline_pos.last + v
|
507
|
+
end
|
508
|
+
@interlace_pass_count << scanline_pos.size
|
509
|
+
# Pass 3
|
510
|
+
v = 1 + (@width / 4.0).ceil * @sample_size
|
511
|
+
((@height - 4) / 8.0).ceil.times do
|
512
|
+
scanline_pos << scanline_pos.last + v
|
513
|
+
end
|
514
|
+
@interlace_pass_count << scanline_pos.size
|
515
|
+
# Pass 4
|
516
|
+
v = 1 + ((@width - 2) / 4.0).ceil * @sample_size
|
517
|
+
(@height / 4.0).ceil.times do
|
518
|
+
scanline_pos << scanline_pos.last + v
|
519
|
+
end
|
520
|
+
@interlace_pass_count << scanline_pos.size
|
521
|
+
# Pass 5
|
522
|
+
v = 1 + (@width / 2.0).ceil * @sample_size
|
523
|
+
((@height - 2) / 4.0).ceil.times do
|
524
|
+
scanline_pos << scanline_pos.last + v
|
525
|
+
end
|
526
|
+
@interlace_pass_count << scanline_pos.size
|
527
|
+
# Pass 6
|
528
|
+
v = 1 + ((@width - 1) / 2.0).ceil * @sample_size
|
529
|
+
(@height / 2.0).ceil.times do
|
530
|
+
scanline_pos << scanline_pos.last + v
|
531
|
+
end
|
532
|
+
@interlace_pass_count << scanline_pos.size
|
533
|
+
# Pass 7
|
534
|
+
v = 1 + @width * @sample_size
|
535
|
+
((@height - 1) / 2.0).ceil.times do
|
536
|
+
scanline_pos << scanline_pos.last + v
|
537
|
+
end
|
538
|
+
scanline_pos.pop # no need to keep last position
|
539
|
+
end
|
540
|
+
loop do
|
541
|
+
v = scanline_pos.last + (1 + @width * @sample_size)
|
542
|
+
break if v >= amount
|
543
|
+
scanline_pos << v
|
544
|
+
end
|
545
|
+
scanline_pos
|
546
|
+
end
|
547
|
+
|
463
548
|
# Makes warning
|
464
549
|
def warn_if_compressed_data_modified # :nodoc:
|
465
550
|
if @is_compressed_data_modified
|
@@ -469,7 +554,7 @@ module PNGlitch
|
|
469
554
|
With this operation, your changes on the compressed data will be reverted.
|
470
555
|
Note that a modification to the compressed data does not reflect to the
|
471
556
|
filtered (decompressed) data.
|
472
|
-
It
|
557
|
+
It's happened around #{trace.last.to_s}
|
473
558
|
EOL
|
474
559
|
message = ["\e[33m", message, "\e[0m"].join if STDOUT.tty? # color yellow
|
475
560
|
warn ["\n", message, "\n"].join
|
data/lib/pnglitch/filter.rb
CHANGED
@@ -10,6 +10,8 @@ module PNGlitch
|
|
10
10
|
AVERAGE = 3
|
11
11
|
PAETH = 4
|
12
12
|
|
13
|
+
@@types = Filter.constants.sort_by {|c| const_get c }.collect(&:downcase)
|
14
|
+
|
13
15
|
#
|
14
16
|
# Guesses and retuens the filter type as a number.
|
15
17
|
#
|
@@ -17,10 +19,10 @@ module PNGlitch
|
|
17
19
|
type = nil
|
18
20
|
if filter_type.is_a?(Numeric) && filter_type.between?(NONE, PAETH)
|
19
21
|
type = filter_type.to_i
|
20
|
-
elsif filter_type.
|
22
|
+
elsif filter_type.is_a?(String) && filter_type =~ /^[0-4]$/
|
21
23
|
type = filter_type.to_i
|
22
24
|
else
|
23
|
-
type = [
|
25
|
+
type = @@types.collect{|c| c.to_s[0] }.index(filter_type.to_s[0].downcase)
|
24
26
|
end
|
25
27
|
type
|
26
28
|
end
|
@@ -28,10 +30,10 @@ module PNGlitch
|
|
28
30
|
attr_reader :filter_type
|
29
31
|
attr_accessor :encoder, :decoder
|
30
32
|
|
31
|
-
def initialize filter_type,
|
32
|
-
@filter_type = Filter.guess
|
33
|
-
@filter_type_name = [
|
34
|
-
@
|
33
|
+
def initialize filter_type, sample_size
|
34
|
+
@filter_type = Filter.guess(filter_type) || 0
|
35
|
+
@filter_type_name = @@types[@filter_type]
|
36
|
+
@sample_size = sample_size
|
35
37
|
@encoder = self.method ('encode_%s' % @filter_type_name.to_s).to_sym
|
36
38
|
@decoder = self.method ('decode_%s' % @filter_type_name.to_s).to_sym
|
37
39
|
end
|
@@ -40,14 +42,14 @@ module PNGlitch
|
|
40
42
|
# Filter with a specified filter type.
|
41
43
|
#
|
42
44
|
def encode data, prev_data = nil
|
43
|
-
@encoder.call data, prev_data
|
45
|
+
@encoder.call data.dup, prev_data
|
44
46
|
end
|
45
47
|
|
46
48
|
#
|
47
49
|
# Reconstruct with a specified filter type.
|
48
50
|
#
|
49
51
|
def decode data, prev_data = nil
|
50
|
-
@decoder.call data, prev_data
|
52
|
+
@decoder.call data.dup, prev_data
|
51
53
|
end
|
52
54
|
|
53
55
|
private
|
@@ -56,40 +58,74 @@ module PNGlitch
|
|
56
58
|
data
|
57
59
|
end
|
58
60
|
|
61
|
+
def decode_none data, prev # :nodoc:
|
62
|
+
data
|
63
|
+
end
|
64
|
+
|
59
65
|
def encode_sub data, prev # :nodoc:
|
60
66
|
# Filt(x) = Orig(x) - Orig(a)
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
d.setbyte i, (x - a) & 0xff
|
67
|
+
data.size.times.reverse_each do |i|
|
68
|
+
next if i < @sample_size
|
69
|
+
x = data.getbyte i
|
70
|
+
a = data.getbyte i - @sample_size
|
71
|
+
data.setbyte i, (x - a) & 0xff
|
67
72
|
end
|
68
|
-
|
73
|
+
data
|
74
|
+
end
|
75
|
+
|
76
|
+
def decode_sub data, prev # :nodoc:
|
77
|
+
# Recon(x) = Filt(x) + Recon(a)
|
78
|
+
data.size.times do |i|
|
79
|
+
next if i < @sample_size
|
80
|
+
x = data.getbyte i
|
81
|
+
a = data.getbyte i - @sample_size
|
82
|
+
data.setbyte i, (x + a) & 0xff
|
83
|
+
end
|
84
|
+
data
|
69
85
|
end
|
70
86
|
|
71
87
|
def encode_up data, prev # :nodoc:
|
72
88
|
# Filt(x) = Orig(x) - Orig(b)
|
73
89
|
return data if prev.nil?
|
74
|
-
|
75
|
-
|
76
|
-
|
90
|
+
data.size.times.reverse_each do |i|
|
91
|
+
x = data.getbyte i
|
92
|
+
b = prev.getbyte i
|
93
|
+
data.setbyte i, (x - b) & 0xff
|
94
|
+
end
|
95
|
+
data
|
96
|
+
end
|
97
|
+
|
98
|
+
def decode_up data, prev # :nodoc:
|
99
|
+
# Recon(x) = Filt(x) + Recon(b)
|
100
|
+
return data if prev.nil?
|
101
|
+
data.size.times do |i|
|
102
|
+
x = data.getbyte i
|
77
103
|
b = prev.getbyte i
|
78
|
-
|
104
|
+
data.setbyte i, (x + b) & 0xff
|
79
105
|
end
|
80
|
-
|
106
|
+
data
|
107
|
+
end
|
108
|
+
|
109
|
+
def decode_average data, prev # :nodoc:
|
110
|
+
# Recon(x) = Filt(x) + floor((Recon(a) + Recon(b)) / 2)
|
111
|
+
data.size.times do |i|
|
112
|
+
x = data.getbyte i
|
113
|
+
a = i >= @sample_size ? data.getbyte(i - @sample_size) : 0
|
114
|
+
b = !prev.nil? ? prev.getbyte(i) : 0
|
115
|
+
data.setbyte i, (x + ((a + b) / 2)) & 0xff
|
116
|
+
end
|
117
|
+
data
|
81
118
|
end
|
82
119
|
|
83
120
|
def encode_average data, prev # :nodoc:
|
84
121
|
# Filt(x) = Orig(x) - floor((Orig(a) + Orig(b)) / 2)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
a = i >= @pixel_size ? d.getbyte(i - @pixel_size) : 0
|
122
|
+
data.size.times.reverse_each do |i|
|
123
|
+
x = data.getbyte i
|
124
|
+
a = i >= @sample_size ? data.getbyte(i - @sample_size) : 0
|
89
125
|
b = !prev.nil? ? prev.getbyte(i) : 0
|
90
|
-
|
126
|
+
data.setbyte i, (x - ((a + b) / 2)) & 0xff
|
91
127
|
end
|
92
|
-
|
128
|
+
data
|
93
129
|
end
|
94
130
|
|
95
131
|
def encode_paeth data, prev # :nodoc:
|
@@ -104,82 +140,40 @@ module PNGlitch
|
|
104
140
|
# else if pb <= pc then Pr = b
|
105
141
|
# else Pr = c
|
106
142
|
# return Pr
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
is_a_exist = i >= @pixel_size
|
143
|
+
data.size.times.reverse_each do |i|
|
144
|
+
x = data.getbyte i
|
145
|
+
is_a_exist = i >= @sample_size
|
111
146
|
is_b_exist = !prev.nil?
|
112
|
-
a = is_a_exist ?
|
147
|
+
a = is_a_exist ? data.getbyte(i - @sample_size) : 0
|
113
148
|
b = is_b_exist ? prev.getbyte(i) : 0
|
114
|
-
c = is_a_exist && is_b_exist ? prev.getbyte(i - @
|
149
|
+
c = is_a_exist && is_b_exist ? prev.getbyte(i - @sample_size) : 0
|
115
150
|
p = a + b - c
|
116
151
|
pa = (p - a).abs
|
117
152
|
pb = (p - b).abs
|
118
153
|
pc = (p - c).abs
|
119
154
|
pr = pa <= pb && pa <= pc ? a : pb <= pc ? b : c
|
120
|
-
|
155
|
+
data.setbyte i, (x - pr) & 0xff
|
121
156
|
end
|
122
|
-
d
|
123
|
-
end
|
124
|
-
|
125
|
-
def decode_none data, prev # :nodoc:
|
126
157
|
data
|
127
158
|
end
|
128
159
|
|
129
|
-
def decode_sub data, prev # :nodoc:
|
130
|
-
# Recon(x) = Filt(x) + Recon(a)
|
131
|
-
d = data.dup
|
132
|
-
d.size.times do |i|
|
133
|
-
next if i < @pixel_size
|
134
|
-
x = d.getbyte i
|
135
|
-
a = d.getbyte i - @pixel_size
|
136
|
-
d.setbyte i, (x + a) & 0xff
|
137
|
-
end
|
138
|
-
d
|
139
|
-
end
|
140
|
-
|
141
|
-
def decode_up data, prev # :nodoc:
|
142
|
-
# Recon(x) = Filt(x) + Recon(b)
|
143
|
-
return data if prev.nil?
|
144
|
-
d = data.dup
|
145
|
-
d.size.times do |i|
|
146
|
-
x = d.getbyte i
|
147
|
-
b = prev.getbyte i
|
148
|
-
d.setbyte i, (x + b) & 0xff
|
149
|
-
end
|
150
|
-
d
|
151
|
-
end
|
152
|
-
|
153
|
-
def decode_average data, prev # :nodoc:
|
154
|
-
# Recon(x) = Filt(x) + floor((Recon(a) + Recon(b)) / 2)
|
155
|
-
d = data.dup
|
156
|
-
d.size.times do |i|
|
157
|
-
x = d.getbyte i
|
158
|
-
a = i >= @pixel_size ? d.getbyte(i - @pixel_size) : 0
|
159
|
-
b = !prev.nil? ? prev.getbyte(i) : 0
|
160
|
-
d.setbyte i, (x + ((a + b) / 2)) & 0xff
|
161
|
-
end
|
162
|
-
d
|
163
|
-
end
|
164
|
-
|
165
160
|
def decode_paeth data, prev # :nodoc:
|
166
161
|
# Recon(x) = Filt(x) + PaethPredictor(Recon(a), Recon(b), Recon(c))
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
is_a_exist = i >= @pixel_size
|
162
|
+
data.size.times do |i|
|
163
|
+
x = data.getbyte i
|
164
|
+
is_a_exist = i >= @sample_size
|
171
165
|
is_b_exist = !prev.nil?
|
172
|
-
a = is_a_exist ?
|
166
|
+
a = is_a_exist ? data.getbyte(i - @sample_size) : 0
|
173
167
|
b = is_b_exist ? prev.getbyte(i) : 0
|
174
|
-
c = is_a_exist && is_b_exist ? prev.getbyte(i - @
|
168
|
+
c = is_a_exist && is_b_exist ? prev.getbyte(i - @sample_size) : 0
|
175
169
|
p = a + b - c
|
176
170
|
pa = (p - a).abs
|
177
171
|
pb = (p - b).abs
|
178
172
|
pc = (p - c).abs
|
179
173
|
pr = (pa <= pb && pa <= pc) ? a : (pb <= pc ? b : c)
|
180
|
-
|
174
|
+
data.setbyte i, (x + pr) & 0xff
|
181
175
|
end
|
182
|
-
|
176
|
+
data
|
183
177
|
end
|
184
178
|
end
|
185
179
|
end
|
data/lib/pnglitch/scanline.rb
CHANGED
@@ -11,12 +11,11 @@ module PNGlitch
|
|
11
11
|
#
|
12
12
|
# Instanciate.
|
13
13
|
#
|
14
|
-
def initialize io, start_at,
|
14
|
+
def initialize io, start_at, data_size, at
|
15
15
|
@index = at
|
16
16
|
@io = io
|
17
17
|
@start_at = start_at
|
18
|
-
@data_size =
|
19
|
-
@sample_size = sample_size
|
18
|
+
@data_size = data_size
|
20
19
|
|
21
20
|
pos = @io.pos
|
22
21
|
@io.pos = @start_at
|
@@ -86,7 +85,7 @@ module PNGlitch
|
|
86
85
|
def change_filter new_filter
|
87
86
|
@prev_filter_type = @filter_type
|
88
87
|
@filter_type = new_filter
|
89
|
-
save
|
88
|
+
self.save
|
90
89
|
end
|
91
90
|
|
92
91
|
#
|
@@ -111,7 +110,6 @@ module PNGlitch
|
|
111
110
|
# the specified filter type value. It takes a Proc object or a block.
|
112
111
|
#
|
113
112
|
def register_filter_decoder decoder = nil, &block
|
114
|
-
@filter_decoder = decoder
|
115
113
|
if !decoder.nil? && decoder.is_a?(Proc)
|
116
114
|
@filter_codec[:decoder] = decoder
|
117
115
|
elsif block_given?
|
@@ -120,14 +118,13 @@ module PNGlitch
|
|
120
118
|
save
|
121
119
|
end
|
122
120
|
|
123
|
-
|
124
121
|
#
|
125
122
|
# Save the changes.
|
126
123
|
#
|
127
124
|
def save
|
128
125
|
pos = @io.pos
|
129
126
|
@io.pos = @start_at
|
130
|
-
@io << [@filter_type].pack('C')
|
127
|
+
@io << [Filter.guess(@filter_type)].pack('C')
|
131
128
|
@io << self.data.slice(0, @data_size).ljust(@data_size, "\0")
|
132
129
|
@io.pos = pos
|
133
130
|
@callback.call(self) unless @callback.nil?
|
data/lib/pnglitch.rb
CHANGED
@@ -20,7 +20,7 @@ require 'pnglitch/base'
|
|
20
20
|
# = Usage
|
21
21
|
#
|
22
22
|
# == Simple glitch
|
23
|
-
#
|
23
|
+
#
|
24
24
|
# png = PNGlitch.open '/path/to/your/image.png'
|
25
25
|
# png.glitch do |data|
|
26
26
|
# data.gsub /\d/, 'x'
|
@@ -39,7 +39,7 @@ require 'pnglitch/base'
|
|
39
39
|
#
|
40
40
|
# The +glitch+ method treats the decompressed data into one String instance. It will be
|
41
41
|
# very convenient, but please note that it could take a huge size of memory. For example,
|
42
|
-
# a normal PNG image in
|
42
|
+
# a normal PNG image in 4000 x 3000 pixels makes over 48 MB of decompressed data.
|
43
43
|
# In case that the memory usage becomes a concern, it can be written to use IO instead of
|
44
44
|
# String.
|
45
45
|
#
|
@@ -64,8 +64,8 @@ require 'pnglitch/base'
|
|
64
64
|
# png.save '/path/to/broken/image.png'
|
65
65
|
# end
|
66
66
|
#
|
67
|
-
#
|
68
|
-
# detected as unopenable
|
67
|
+
# Depending a viewer application, the result of the first example using +glitch+ can be
|
68
|
+
# detected as unopenable, because of breaking the filter type bytes (Most applications
|
69
69
|
# will ignore it, but I found the library in java.awt get failed). The operation with
|
70
70
|
# +each_scanline+ will be more careful for memory usage and the file itself.
|
71
71
|
# It is a polite way, but is slower than the rude +glitch+.
|
@@ -89,33 +89,34 @@ require 'pnglitch/base'
|
|
89
89
|
# end
|
90
90
|
#
|
91
91
|
# Filter is a tiny function for optimizing PNG compression. It can be set different types
|
92
|
-
# with each scanline. The five filter types are defined in the spec, are named +None+,
|
93
|
-
# +Sub+, +Average+ and +Paeth+ (+None+ means no filter, this filter type makes "raw"
|
94
|
-
# Internally five digits (0-4)
|
92
|
+
# with each scanline. The five filter types are defined in the spec, are named +None+,
|
93
|
+
# +Sub+, +Up+, +Average+ and +Paeth+ (+None+ means no filter, this filter type makes "raw"
|
94
|
+
# data). Internally five digits (0-4) correspond them.
|
95
95
|
#
|
96
96
|
# The filter types must be the most important factor behind the representation of glitch
|
97
|
-
# results. Each filter has different effect.
|
97
|
+
# results. Each filter type has different effect.
|
98
98
|
#
|
99
|
-
# Generally in PNG file, scanlines has a variety of filter types on each
|
99
|
+
# Generally in PNG file, scanlines has a variety of filter types on each, as in a
|
100
100
|
# convertion by image processing applications (like Photoshop or ImageMagick) they try to
|
101
|
-
# apply proper filter
|
101
|
+
# apply a proper filter type with each scanline.
|
102
102
|
#
|
103
103
|
# You can check the values like:
|
104
104
|
#
|
105
105
|
# puts png.filter_types
|
106
106
|
#
|
107
|
-
# With +each_scanline+, we can reach the
|
107
|
+
# With +each_scanline+, we can reach the filter types particularly.
|
108
108
|
#
|
109
109
|
# png.each_scanline do |scanline|
|
110
|
+
# puts scanline.filter_type
|
110
111
|
# scanline.change_filter 3
|
111
112
|
# end
|
112
113
|
#
|
113
114
|
# The example above puts all filter types in 3 (type +Average+). +change_filter+ will
|
114
115
|
# apply new filter type values correctly. It computes filters and makes the PNG well
|
115
|
-
# formatted, and any glitch won't get happened. It also means the output image
|
116
|
-
# completely
|
116
|
+
# formatted, and any glitch won't get happened. It also means the output image should
|
117
|
+
# completely look the same as the input one.
|
117
118
|
#
|
118
|
-
# However glitches
|
119
|
+
# However glitches will reveal the difference of the filter types.
|
119
120
|
#
|
120
121
|
# PNGlitch.open(infile) do |png|
|
121
122
|
# png.each_scanline do |scanline|
|
@@ -139,10 +140,10 @@ require 'pnglitch/base'
|
|
139
140
|
#
|
140
141
|
# With the results of the example above, obviously we can recognize the filter types make
|
141
142
|
# a big difference. The filter is distinct and interesting thing in PNG glitching.
|
142
|
-
# To put all filter type in a same value before glitching, we
|
143
|
-
# taste of each filter type. (Note that +change_filter+
|
144
|
-
# processing libraries like ImageMagick also have
|
145
|
-
# same ones and they
|
143
|
+
# To put all filter type in a same value before glitching, we would see the signature
|
144
|
+
# taste of each filter type. (Note that +change_filter+ may be a little bit slow, image
|
145
|
+
# processing libraries like ImageMagick also have an option to put all filter type in
|
146
|
+
# same ones and they may process faster.)
|
146
147
|
#
|
147
148
|
# This library provides a simple method to change the filter type so that generating all
|
148
149
|
# possible effects in PNG glitch.
|
@@ -159,13 +160,12 @@ require 'pnglitch/base'
|
|
159
160
|
#
|
160
161
|
# png.each_scanline do |scanline|
|
161
162
|
# scanline.register_filter_encoder do |data, prev|
|
162
|
-
#
|
163
|
-
#
|
164
|
-
# x = d.getbyte(i)
|
163
|
+
# data.size.times.reverse_each do |i|
|
164
|
+
# x = data.getbyte(i)
|
165
165
|
# v = prev ? prev.getbyte((i - 5).abs) : 0
|
166
|
-
#
|
166
|
+
# data.setbyte(i, (x - v) & 0xff)
|
167
167
|
# end
|
168
|
-
#
|
168
|
+
# data
|
169
169
|
# end
|
170
170
|
# end
|
171
171
|
#
|
@@ -178,7 +178,7 @@ require 'pnglitch/base'
|
|
178
178
|
# +----------+ +---------------+ +-----------------+ +----------------+
|
179
179
|
#
|
180
180
|
# It shows that there are two states between raw data and a result file, and it means there
|
181
|
-
# are
|
181
|
+
# are two states possible to glitch. This library provides to choose of the state to glitch.
|
182
182
|
#
|
183
183
|
# All examples cited thus far are operations to "filtered data". On the other hand, PNGlitch
|
184
184
|
# can touch the "compressed data" through +glitch_after_compress+ method:
|
@@ -189,10 +189,10 @@ require 'pnglitch/base'
|
|
189
189
|
# end
|
190
190
|
#
|
191
191
|
# Glitch against the compressed data makes slightly different pictures from other results.
|
192
|
-
# But sometimes this scratch
|
192
|
+
# But sometimes this scratch could break the compression and make the file unopenable.
|
193
193
|
#
|
194
194
|
module PNGlitch
|
195
|
-
VERSION = '0.0.
|
195
|
+
VERSION = '0.0.2'
|
196
196
|
|
197
197
|
class << self
|
198
198
|
|
@@ -222,10 +222,11 @@ module PNGlitch
|
|
222
222
|
# the amount of pixels).
|
223
223
|
# To avoid the attack known as "zip bomb", PNGlitch will throw an error when
|
224
224
|
# decompressed data goes over twice the expected size. If it's sure that the passed
|
225
|
-
# file is safe, the upper limit of decompressed data size can be set in +open+'s option
|
225
|
+
# file is safe, the upper limit of decompressed data size can be set in +open+'s option
|
226
|
+
# in bytes.
|
226
227
|
# Like:
|
227
228
|
#
|
228
|
-
# PNGlitch.open(infile, limit_of_decompressed_data_size:
|
229
|
+
# PNGlitch.open(infile, limit_of_decompressed_data_size: 1024 ** 3)
|
229
230
|
#
|
230
231
|
def open file, options = {}
|
231
232
|
base = Base.new file, options[:limit_of_decompressed_data_size]
|
data/pnglitch.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = PNGlitch::VERSION
|
9
9
|
spec.authors = ["ucnv"]
|
10
10
|
spec.email = ["ucnvvv@gmail.com"]
|
11
|
-
spec.
|
12
|
-
spec.
|
11
|
+
spec.description = %q{A Ruby library to glitch PNG images.}
|
12
|
+
spec.summary = <<-EOL.gsub(/^\s*/, '')
|
13
13
|
PNGlitch is a Ruby library to destroy your PNG images.
|
14
14
|
With normal data-bending technique, a glitch against PNG will easily fail
|
15
15
|
because of the checksum function. We provide a fail-proof destruction for it.
|
Binary file
|
File without changes
|
Binary file
|
data/spec/pnglitch_spec.rb
CHANGED
@@ -49,6 +49,7 @@ describe PNGlitch do
|
|
49
49
|
end
|
50
50
|
}.not_to raise_error
|
51
51
|
expect(types.size).to eq h
|
52
|
+
expect(outfile).to exist
|
52
53
|
end
|
53
54
|
|
54
55
|
it 'should return a value of last call in the block' do
|
@@ -68,9 +69,17 @@ describe PNGlitch do
|
|
68
69
|
end
|
69
70
|
end
|
70
71
|
|
71
|
-
context
|
72
|
-
it 'should raise error' do
|
73
|
-
bomb = infile.dirname.join('
|
72
|
+
context 'when decompressed data is unexpected size' do
|
73
|
+
it 'should not raise error for too small size' do
|
74
|
+
bomb = infile.dirname.join('ina.png')
|
75
|
+
expect {
|
76
|
+
png = PNGlitch.open bomb
|
77
|
+
png.close
|
78
|
+
}.to_not raise_error
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should raise error for too large size' do
|
82
|
+
bomb = infile.dirname.join('inb.png')
|
74
83
|
expect {
|
75
84
|
png = PNGlitch.open bomb
|
76
85
|
png.close
|
@@ -78,15 +87,16 @@ describe PNGlitch do
|
|
78
87
|
end
|
79
88
|
|
80
89
|
it 'can avoid the error' do
|
81
|
-
bomb = infile.dirname.join('
|
90
|
+
bomb = infile.dirname.join('inb.png')
|
82
91
|
expect {
|
83
92
|
png = PNGlitch.open bomb, limit_of_decompressed_data_size: 100 * 1024 ** 2
|
84
93
|
png.close
|
85
94
|
}.not_to raise_error
|
86
95
|
end
|
96
|
+
|
87
97
|
end
|
88
98
|
|
89
|
-
context
|
99
|
+
context 'when it is not PNG file' do
|
90
100
|
it 'should raise error' do
|
91
101
|
file = infile.dirname.join('filter_none')
|
92
102
|
expect {
|
@@ -95,6 +105,20 @@ describe PNGlitch do
|
|
95
105
|
}.to raise_error PNGlitch::FormatError
|
96
106
|
end
|
97
107
|
end
|
108
|
+
|
109
|
+
context 'with interlaced image' do
|
110
|
+
it 'should check it' do
|
111
|
+
a = PNGlitch.open infile do |p|
|
112
|
+
p.interlaced?
|
113
|
+
end
|
114
|
+
interlace = infile.dirname.join('inc.png')
|
115
|
+
b = PNGlitch.open interlace do |p|
|
116
|
+
p.interlaced?
|
117
|
+
end
|
118
|
+
expect(a).to be false
|
119
|
+
expect(b).to be true
|
120
|
+
end
|
121
|
+
end
|
98
122
|
end
|
99
123
|
|
100
124
|
describe '.output' do
|
@@ -155,6 +179,19 @@ describe PNGlitch do
|
|
155
179
|
end
|
156
180
|
end
|
157
181
|
|
182
|
+
describe '.apply_filters' do
|
183
|
+
it 'should ignore wrong filter types' do
|
184
|
+
PNGlitch.open infile do |p|
|
185
|
+
io = p.filtered_data
|
186
|
+
io.rewind
|
187
|
+
io << [100].pack('C')
|
188
|
+
expect {
|
189
|
+
p.apply_filters
|
190
|
+
}.not_to raise_error
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
158
195
|
describe '.glitch' do
|
159
196
|
it 'makes a result that is readable as PNG' do
|
160
197
|
png = PNGlitch.open infile
|
@@ -290,6 +327,15 @@ describe PNGlitch do
|
|
290
327
|
end
|
291
328
|
|
292
329
|
describe '.filter_types' do
|
330
|
+
it 'should be in range between 0 and 4' do
|
331
|
+
png = PNGlitch.open infile
|
332
|
+
types = png.filter_types
|
333
|
+
png.close
|
334
|
+
types.each do |t|
|
335
|
+
expect(t).to be_between(0, 4)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
293
339
|
it 'should be same size of image height' do
|
294
340
|
png = PNGlitch.open infile
|
295
341
|
types = png.filter_types
|
@@ -297,6 +343,19 @@ describe PNGlitch do
|
|
297
343
|
png.close
|
298
344
|
expect(types.size).to eq height
|
299
345
|
end
|
346
|
+
|
347
|
+
context 'with interlaced PNG' do
|
348
|
+
it 'should be in range between 0 and 4' do
|
349
|
+
png = PNGlitch.open infile.dirname.join('inc.png')
|
350
|
+
types = png.filter_types
|
351
|
+
png.close
|
352
|
+
types.each do |t|
|
353
|
+
expect(t).to be_between(0, 4)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
end
|
358
|
+
|
300
359
|
end
|
301
360
|
|
302
361
|
describe '.each_scanline' do
|
@@ -365,6 +424,36 @@ describe PNGlitch do
|
|
365
424
|
end
|
366
425
|
end
|
367
426
|
|
427
|
+
it 'can change filter type with the name' do
|
428
|
+
t1 = PNGlitch.open infile do |p|
|
429
|
+
p.each_scanline do |l|
|
430
|
+
l.change_filter PNGlitch::Filter::PAETH
|
431
|
+
end
|
432
|
+
p.filter_types
|
433
|
+
end
|
434
|
+
t2 = PNGlitch.open infile do |p|
|
435
|
+
p.each_scanline do |l|
|
436
|
+
l.change_filter :paeth
|
437
|
+
end
|
438
|
+
p.filter_types
|
439
|
+
end
|
440
|
+
t3 = PNGlitch.open infile do |p|
|
441
|
+
p.each_scanline do |l|
|
442
|
+
l.change_filter :sub
|
443
|
+
end
|
444
|
+
p.filter_types
|
445
|
+
end
|
446
|
+
t4 = PNGlitch.open infile do |p|
|
447
|
+
p.each_scanline do |l|
|
448
|
+
l.graft 'Paeth'
|
449
|
+
end
|
450
|
+
p.filter_types
|
451
|
+
end
|
452
|
+
expect(t2).to be == t1
|
453
|
+
expect(t3).not_to be == t1
|
454
|
+
expect(t4).to be == t1
|
455
|
+
end
|
456
|
+
|
368
457
|
it 'can apply custom filter method' do
|
369
458
|
lines = []
|
370
459
|
sample_size = nil
|
@@ -381,17 +470,16 @@ describe PNGlitch do
|
|
381
470
|
end
|
382
471
|
|
383
472
|
enc = lambda do |data, prev|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
a = i >= sample_size ? d.getbyte(i - sample_size - 1) : 0
|
473
|
+
data.size.times.reverse_each do |i|
|
474
|
+
x = data.getbyte i
|
475
|
+
a = i >= sample_size ? data.getbyte(i - sample_size - 1) : 0
|
388
476
|
b = !prev.nil? ? prev.getbyte(i - 1) : 0
|
389
|
-
|
477
|
+
data.setbyte i, (x - ((a + b) / 2)) & 0xff
|
390
478
|
end
|
391
|
-
|
479
|
+
data
|
392
480
|
end
|
393
481
|
decoded = PNGlitch::Filter.new(original_filter, sample_size).decode(lines[1], lines[0])
|
394
|
-
encoded = enc.call(decoded, lines[0])
|
482
|
+
encoded = enc.call(decoded.dup, lines[0])
|
395
483
|
|
396
484
|
PNGlitch.open infile do |png|
|
397
485
|
png.each_scanline.with_index do |s, i|
|
@@ -408,16 +496,15 @@ describe PNGlitch do
|
|
408
496
|
# ==================================
|
409
497
|
|
410
498
|
dec = lambda do |data, prev|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
a = i >= sample_size ? d.getbyte(i - sample_size - 2) : 0
|
499
|
+
data.size.times do |i|
|
500
|
+
x = data.getbyte i
|
501
|
+
a = i >= sample_size ? data.getbyte(i - sample_size - 2) : 0
|
415
502
|
b = !prev.nil? ? prev.getbyte(i - 1) : 0
|
416
|
-
|
503
|
+
data.setbyte i, (x + ((a + b) / 2)) & 0xff
|
417
504
|
end
|
418
|
-
|
505
|
+
data
|
419
506
|
end
|
420
|
-
decoded = dec.call(lines[1], lines[0])
|
507
|
+
decoded = dec.call(lines[1].dup, lines[0])
|
421
508
|
encoded = PNGlitch::Filter.new(original_filter, sample_size).encode(decoded, lines[0])
|
422
509
|
|
423
510
|
PNGlitch.open infile do |png|
|
@@ -434,8 +521,8 @@ describe PNGlitch do
|
|
434
521
|
|
435
522
|
# ==================================
|
436
523
|
|
437
|
-
decoded = dec.call(lines[1], lines[0])
|
438
|
-
encoded = enc.call(decoded, lines[0])
|
524
|
+
decoded = dec.call(lines[1].dup, lines[0])
|
525
|
+
encoded = enc.call(decoded.dup, lines[0])
|
439
526
|
|
440
527
|
PNGlitch.open infile do |png|
|
441
528
|
png.each_scanline.with_index do |s, i|
|
@@ -503,6 +590,22 @@ describe PNGlitch do
|
|
503
590
|
expect(b).not_to eq(a)
|
504
591
|
end
|
505
592
|
end
|
593
|
+
|
594
|
+
context 'with an interlaced image' do
|
595
|
+
it 'can recognize correct filter types' do
|
596
|
+
img = infile.dirname.join('inc.png')
|
597
|
+
PNGlitch.open img do |p|
|
598
|
+
p.each_scanline do |line|
|
599
|
+
expect(line.filter_type).to be_between(0, 4)
|
600
|
+
end
|
601
|
+
p.change_all_filters 4
|
602
|
+
p.each_scanline do |line|
|
603
|
+
expect(line.filter_type).to be == 4
|
604
|
+
end
|
605
|
+
p.save outfile.dirname.join('i.png')
|
606
|
+
end
|
607
|
+
end
|
608
|
+
end
|
506
609
|
end
|
507
610
|
|
508
611
|
describe '.scanline_at' do
|
@@ -542,17 +645,16 @@ describe PNGlitch do
|
|
542
645
|
end
|
543
646
|
|
544
647
|
enc = lambda do |data, prev|
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
a = i >= sample_size ? d.getbyte(i - sample_size - 1) : 0
|
648
|
+
data.size.times.reverse_each do |i|
|
649
|
+
x = data.getbyte i
|
650
|
+
a = i >= sample_size ? data.getbyte(i - sample_size - 1) : 0
|
549
651
|
b = !prev.nil? ? prev.getbyte(i - 2) : 0
|
550
|
-
|
652
|
+
data.setbyte i, (x - ((a + b) / 2)) & 0xff
|
551
653
|
end
|
552
|
-
|
654
|
+
data
|
553
655
|
end
|
554
656
|
decoded = PNGlitch::Filter.new(original_filter, sample_size).decode(lines[1], lines[0])
|
555
|
-
encoded = enc.call(decoded, lines[0])
|
657
|
+
encoded = enc.call(decoded.dup, lines[0])
|
556
658
|
|
557
659
|
PNGlitch.open infile do |png|
|
558
660
|
l = png.scanline_at 100
|
@@ -582,10 +684,27 @@ describe PNGlitch do
|
|
582
684
|
end
|
583
685
|
end
|
584
686
|
|
687
|
+
describe '.change_all_filters' do
|
688
|
+
it 'can change filter types with single method' do
|
689
|
+
v = rand(4)
|
690
|
+
PNGlitch.open infile do |p|
|
691
|
+
p.change_all_filters v
|
692
|
+
p.save outfile
|
693
|
+
end
|
694
|
+
PNGlitch.open outfile do |p|
|
695
|
+
p.each_scanline do |l|
|
696
|
+
expect(l.filter_type).to be == v
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
|
701
|
+
end
|
702
|
+
end
|
703
|
+
|
585
704
|
describe '.width and .height' do
|
586
705
|
it 'destroy the dimension of the image' do
|
587
706
|
w, h = ()
|
588
|
-
out =
|
707
|
+
out = outfile
|
589
708
|
PNGlitch.open infile do |p|
|
590
709
|
w = p.width
|
591
710
|
h = p.height
|
@@ -600,4 +719,36 @@ describe PNGlitch do
|
|
600
719
|
end
|
601
720
|
end
|
602
721
|
|
722
|
+
describe '.idat_chunk_size' do
|
723
|
+
it 'should be controllable' do
|
724
|
+
amount = nil
|
725
|
+
out1 = outdir.join 'a.png'
|
726
|
+
out2 = outdir.join 'b.png'
|
727
|
+
PNGlitch.open infile do |p|
|
728
|
+
amount = p.compressed_data.size
|
729
|
+
p.idat_chunk_size = 1024
|
730
|
+
p.output out1
|
731
|
+
end
|
732
|
+
PNGlitch.open out1 do |p|
|
733
|
+
expect(p.idat_chunk_size).to be == 1024
|
734
|
+
end
|
735
|
+
idat_size = open(out1, 'rb') do |f|
|
736
|
+
f.read.scan(/IDAT/).size
|
737
|
+
end
|
738
|
+
expect(idat_size).to be == (amount.to_f / 1024).ceil
|
739
|
+
|
740
|
+
PNGlitch.open infile do |p|
|
741
|
+
p.idat_chunk_size = nil
|
742
|
+
p.output out2
|
743
|
+
end
|
744
|
+
PNGlitch.open out2 do |p|
|
745
|
+
expect(p.idat_chunk_size).to be nil
|
746
|
+
end
|
747
|
+
idat_size = open(out2, 'rb') do |f|
|
748
|
+
f.read.scan(/IDAT/).size
|
749
|
+
end
|
750
|
+
expect(idat_size).to be == 1
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
603
754
|
end
|
metadata
CHANGED
@@ -1,62 +1,58 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pnglitch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ucnv
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-04-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.7'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.7'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '10.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - ~>
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '10.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
description:
|
56
|
-
PNGlitch is a Ruby library to destroy your PNG images.
|
57
|
-
With normal data-bending technique, a glitch against PNG will easily fail
|
58
|
-
because of the checksum function. We provide a fail-proof destruction for it.
|
59
|
-
Using this library you will see beautiful and various PNG artifacts.
|
55
|
+
description: A Ruby library to glitch PNG images.
|
60
56
|
email:
|
61
57
|
- ucnvvv@gmail.com
|
62
58
|
executables:
|
@@ -64,9 +60,9 @@ executables:
|
|
64
60
|
extensions: []
|
65
61
|
extra_rdoc_files: []
|
66
62
|
files:
|
67
|
-
- .gitignore
|
68
|
-
- .rspec
|
69
|
-
- .travis.yml
|
63
|
+
- ".gitignore"
|
64
|
+
- ".rspec"
|
65
|
+
- ".travis.yml"
|
70
66
|
- Gemfile
|
71
67
|
- LICENSE.txt
|
72
68
|
- README.md
|
@@ -78,13 +74,15 @@ files:
|
|
78
74
|
- lib/pnglitch/filter.rb
|
79
75
|
- lib/pnglitch/scanline.rb
|
80
76
|
- pnglitch.gemspec
|
81
|
-
- spec/fixtures/bomb.png
|
82
77
|
- spec/fixtures/filter_average
|
83
78
|
- spec/fixtures/filter_none
|
84
79
|
- spec/fixtures/filter_paeth
|
85
80
|
- spec/fixtures/filter_sub
|
86
81
|
- spec/fixtures/filter_up
|
87
82
|
- spec/fixtures/in.png
|
83
|
+
- spec/fixtures/ina.png
|
84
|
+
- spec/fixtures/inb.png
|
85
|
+
- spec/fixtures/inc.png
|
88
86
|
- spec/pnglitch_filter_spec.rb
|
89
87
|
- spec/pnglitch_spec.rb
|
90
88
|
- spec/spec_helper.rb
|
@@ -98,28 +96,33 @@ require_paths:
|
|
98
96
|
- lib
|
99
97
|
required_ruby_version: !ruby/object:Gem::Requirement
|
100
98
|
requirements:
|
101
|
-
- -
|
99
|
+
- - ">="
|
102
100
|
- !ruby/object:Gem::Version
|
103
101
|
version: 2.0.0
|
104
102
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
103
|
requirements:
|
106
|
-
- -
|
104
|
+
- - ">="
|
107
105
|
- !ruby/object:Gem::Version
|
108
106
|
version: '0'
|
109
107
|
requirements: []
|
110
108
|
rubyforge_project:
|
111
|
-
rubygems_version: 2.
|
109
|
+
rubygems_version: 2.4.5
|
112
110
|
signing_key:
|
113
111
|
specification_version: 4
|
114
|
-
summary:
|
112
|
+
summary: PNGlitch is a Ruby library to destroy your PNG images. With normal data-bending
|
113
|
+
technique, a glitch against PNG will easily fail because of the checksum function.
|
114
|
+
We provide a fail-proof destruction for it. Using this library you will see beautiful
|
115
|
+
and various PNG artifacts.
|
115
116
|
test_files:
|
116
|
-
- spec/fixtures/bomb.png
|
117
117
|
- spec/fixtures/filter_average
|
118
118
|
- spec/fixtures/filter_none
|
119
119
|
- spec/fixtures/filter_paeth
|
120
120
|
- spec/fixtures/filter_sub
|
121
121
|
- spec/fixtures/filter_up
|
122
122
|
- spec/fixtures/in.png
|
123
|
+
- spec/fixtures/ina.png
|
124
|
+
- spec/fixtures/inb.png
|
125
|
+
- spec/fixtures/inc.png
|
123
126
|
- spec/pnglitch_filter_spec.rb
|
124
127
|
- spec/pnglitch_spec.rb
|
125
128
|
- spec/spec_helper.rb
|