diff --git a/example/asset/issue28/issue28-1.png b/example/asset/issue28/issue28-1.png new file mode 100644 index 0000000..1d77fb0 Binary files /dev/null and b/example/asset/issue28/issue28-1.png differ diff --git a/example/asset/issue28/issue28-2.png b/example/asset/issue28/issue28-2.png new file mode 100644 index 0000000..55efaf8 Binary files /dev/null and b/example/asset/issue28/issue28-2.png differ diff --git a/example/test/issue_028_test.dart b/example/test/issue_028_test.dart new file mode 100644 index 0000000..21b90dd --- /dev/null +++ b/example/test/issue_028_test.dart @@ -0,0 +1,114 @@ +import 'dart:io'; + +import 'package:image_size_getter/file_input.dart'; +import 'package:image_size_getter/image_size_getter.dart'; +import 'package:test/test.dart'; + +void main() { + group('PNG with trailing bytes test', () { + late List pngFiles; + + setUpAll(() { + final dir = Directory('asset/issue28'); + pngFiles = dir + .listSync() + .whereType() + .where((file) => file.path.endsWith('.png')) + .toList(); + + expect(pngFiles.isNotEmpty, true, + reason: 'No PNG files found in asset/issue28/'); + + print('Found ${pngFiles.length} PNG files to test'); + }); + + test('all PNG files should work with non-standard mode', () { + final nonStandardDecoder = PngDecoder(isStandardPng: false); + ImageSizeGetter.registerDecoder(nonStandardDecoder); + + for (final file in pngFiles) { + print('\nTesting: ${file.path}'); + + final fileInput = FileInput(file); + final result = ImageSizeGetter.getSizeResult( + fileInput, + ); + + print(' Size: ${result.size.width}x${result.size.height}'); + print(' Decoded by: ${result.decoder.decoderName}'); + + expect(result.size.width, greaterThan(0)); + expect(result.size.height, greaterThan(0)); + expect(result.decoder.decoderName, 'non-standard-png'); + } + }); + + test('PNG with trailing bytes should fail in standard mode', () { + final standardDecoder = PngDecoder(isStandardPng: true); + ImageSizeGetter.registerDecoder(standardDecoder); + // Find the bug PNG (the one with trailing bytes) + final bugPng = pngFiles.firstWhere( + (f) => f.path.contains('bug') || f.path.contains('trailing'), + orElse: () => pngFiles.last, // Assume last one is the bug + ); + + print('\nTesting bug PNG with standard mode: ${bugPng.path}'); + + // Standard mode should reject it + expect( + () => ImageSizeGetter.getSizeResult( + FileInput(bugPng), + ), + throwsA(isA()), + reason: 'Standard mode should reject PNG with trailing bytes', + ); + + print(' ✓ Correctly rejected by standard mode'); + }); + + test('same PNG should succeed with non-standard mode', () { + final nonStandardDecoder = PngDecoder(isStandardPng: false); + ImageSizeGetter.registerDecoder(nonStandardDecoder); + // Find the bug PNG + final bugPng = pngFiles.firstWhere( + (f) => f.path.contains('bug') || f.path.contains('trailing'), + orElse: () => pngFiles.last, + ); + + print('\nTesting bug PNG with non-standard mode: ${bugPng.path}'); + + // Non-standard mode should accept it + final result = ImageSizeGetter.getSizeResult( + FileInput(bugPng) + ); + + print(' Size: ${result.size.width}x${result.size.height}'); + print(' ✓ Successfully decoded'); + + expect(result.size.width, greaterThan(0)); + expect(result.size.height, greaterThan(0)); + }); + + test('default PngDecoder should use non-standard mode', () { + // When no parameter is passed, should default to non-standard (lenient) + final defaultDecoder = PngDecoder(); + ImageSizeGetter.registerDecoder(defaultDecoder); + for (final file in pngFiles) { + final result = ImageSizeGetter.getSizeResult( + FileInput(file), + ); + + expect(result.size.width, greaterThan(0)); + expect(result.decoder.decoderName, 'non-standard-png'); + } + }); + + test('verify decoder names', () { + final standard = PngDecoder(isStandardPng: true); + final nonStandard = PngDecoder(isStandardPng: false); + + expect(standard.decoderName, 'png'); + expect(nonStandard.decoderName, 'non-standard-png'); + }); + }); +} \ No newline at end of file diff --git a/packages/image_size_getter/lib/src/decoder/impl/png_decoder.dart b/packages/image_size_getter/lib/src/decoder/impl/png_decoder.dart index 277b0ec..801230c 100644 --- a/packages/image_size_getter/lib/src/decoder/impl/png_decoder.dart +++ b/packages/image_size_getter/lib/src/decoder/impl/png_decoder.dart @@ -7,10 +7,16 @@ import 'package:image_size_getter/image_size_getter.dart'; /// {@endtemplate} class PngDecoder extends BaseDecoder with SimpleTypeValidator { /// {@macro image_size_getter.PngDecoder} - const PngDecoder(); + + const PngDecoder({ + this.isStandardPng = false, // Default to lenient + }); + + final bool isStandardPng; @override - String get decoderName => 'png'; + String get decoderName => isStandardPng ? 'png' : 'non-standard-png'; + @override List get supportedExtensions => List.unmodifiable(['png']); @@ -36,10 +42,11 @@ class PngDecoder extends BaseDecoder with SimpleTypeValidator { } @override - SimpleFileHeaderAndFooter get simpleFileHeaderAndFooter => _PngHeaders(); + SimpleFileHeaderAndFooter get simpleFileHeaderAndFooter => + isStandardPng ? _StandardPngHeaders() : _NonStandardPngHeaders(); } -class _PngHeaders with SimpleFileHeaderAndFooter { +class _StandardPngHeaders with SimpleFileHeaderAndFooter { static const sig = [ 0x89, 0x50, @@ -72,3 +79,31 @@ class _PngHeaders with SimpleFileHeaderAndFooter { @override List get startBytes => sig; } + +/// Non-standard PNG Info +/// +/// Some PNG files have trailing bytes after the IEND chunk. +/// +/// These files are technically invalid per PNG specification, but are +/// commonly produced by various tools and should be supported for +/// interoperability with other image libraries. +class _NonStandardPngHeaders with SimpleFileHeaderAndFooter { + static const sig = [ + 0x89, + 0x50, + 0x4E, + 0x47, + 0x0D, + 0x0A, + 0x1A, + 0x0A, + ]; + + static const iend = []; + + @override + List get endBytes => iend; + + @override + List get startBytes => sig; +}