Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added example/asset/issue28/issue28-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/asset/issue28/issue28-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
114 changes: 114 additions & 0 deletions example/test/issue_028_test.dart
Original file line number Diff line number Diff line change
@@ -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<File> pngFiles;

setUpAll(() {
final dir = Directory('asset/issue28');
pngFiles = dir
.listSync()
.whereType<File>()
.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<UnsupportedError>()),
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');
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> get supportedExtensions => List.unmodifiable(['png']);
Expand All @@ -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,
Expand Down Expand Up @@ -72,3 +79,31 @@ class _PngHeaders with SimpleFileHeaderAndFooter {
@override
List<int> 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 = <int>[];

@override
List<int> get endBytes => iend;

@override
List<int> get startBytes => sig;
}