pnglitch 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|