diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fa701a11..a4cc04d5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Fixed - Flash viewer - subtract blend mode +- [#1712], [#1857] JPEG images errors fixer ## [16.0.4] - 2022-11-03 ### Fixed @@ -2339,7 +2340,8 @@ All notable changes to this project will be documented in this file. ### Added - Initial public release -[Unreleased]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version16.0.3...dev +[Unreleased]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version16.0.4...dev +[16.0.4]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version16.0.3...version16.0.4 [16.0.3]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version16.0.2...version16.0.3 [16.0.2]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version16.0.1...version16.0.2 [16.0.1]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version16.0.0...version16.0.1 @@ -2464,6 +2466,8 @@ All notable changes to this project will be documented in this file. [alpha 9]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha8...alpha9 [alpha 8]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha7...alpha8 [alpha 7]: https://github.com/jindrapetrik/jpexs-decompiler/releases/tag/alpha7 +[#1712]: https://www.free-decompiler.com/flash/issues/1712 +[#1857]: https://www.free-decompiler.com/flash/issues/1857 [#1860]: https://www.free-decompiler.com/flash/issues/1860 [#1782]: https://www.free-decompiler.com/flash/issues/1782 [#1679]: https://www.free-decompiler.com/flash/issues/1679 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG2Tag.java index 115837033..d1addbed2 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG2Tag.java @@ -126,11 +126,10 @@ public class DefineBitsJPEG2Tag extends ImageTag implements AloneTag { @Override public InputStream getOriginalImageData() { - int errorLength = hasErrorHeader(imageData) ? 4 : 0; JpegFixer jpegFixer = new JpegFixer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - jpegFixer.fixJpeg(new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength), baos); + jpegFixer.fixJpeg(new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()), baos); } catch (IOException ex) { Logger.getLogger(DefineBitsJPEG2Tag.class.getName()).log(Level.SEVERE, null, ex); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java index fb7b39998..1311c6b01 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java @@ -175,11 +175,10 @@ public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag { @Override public InputStream getOriginalImageData() { if (bitmapAlphaData.getLength() == 0) { // No alpha - int errorLength = hasErrorHeader(imageData) ? 4 : 0; JpegFixer jpegFixer = new JpegFixer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - jpegFixer.fixJpeg(new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength), baos); + jpegFixer.fixJpeg(new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()), baos); } catch (IOException ex) { Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, null, ex); } @@ -192,11 +191,10 @@ public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag { @Override protected SerializableImage getImage() { try { - int errorLength = hasErrorHeader(imageData) ? 4 : 0; JpegFixer jpegFixer = new JpegFixer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - jpegFixer.fixJpeg(new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength), baos); + jpegFixer.fixJpeg(new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()), baos); } catch (IOException ex) { Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, null, ex); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/JpegFixer.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/JpegFixer.java index 36feaf102..68469eb65 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/helpers/JpegFixer.java +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/JpegFixer.java @@ -1,53 +1,37 @@ package com.jpexs.helpers; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; /** * Fixes probles in some JPEGs to be readable by standard viewers. * + * It removes EOI markers followed by SOI markers. It does it also on the + * beginning of the file. + * * @author JPEXS */ public class JpegFixer { - private static final int SOI = 0xD8; - private static final int EOI = 0xD9; - private static final int SOF0 = 0xC0; - private static final int SOF1 = 0xC1; - private static final int SOF2 = 0xC2; - private static final int SOF3 = 0xC3; - private static final int SOF5 = 0xC5; - private static final int SOF6 = 0xC6; - private static final int SOF7 = 0xC7; - private static final int SOF9 = 0xC9; - private static final int SOF10 = 0xCA; - private static final int SOF11 = 0xCB; - private static final int SOF13 = 0xCD; - private static final int SOF14 = 0xCE; - private static final int SOF15 = 0xCF; - - private static final int APP0 = 0xE0; - + public static final int SOI = 0xD8; + public static final int EOI = 0xD9; public void fixJpeg(InputStream is, OutputStream os) throws IOException { - List data = new ArrayList<>(); - List markers = new ArrayList<>(); + boolean prevEoi = false; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int lastMarker = -1; int val = is.read(); if (val == -1) { return; } if (val == 0xFF) { val = is.read(); - if (val != SOI) { - //not a JPEG file, proceed as is + if (val == -1) { + os.write(0xFF); + return; + } + if (val != SOI && val != EOI) { + //not a JPEG file, nor invalid header, proceed as is os.write(0xFF); os.write(val); while ((val = is.read()) > -1) { @@ -55,6 +39,48 @@ public class JpegFixer { } return; } + //Check for errorneous header at the beginning, before first SOI marker + if (val == EOI) { + val = is.read(); + int val2 = is.read(); + if (val == 0xFF && val2 == SOI) { + val = is.read(); + val2 = is.read(); + if (val != 0xFF || val2 != SOI) { + //not a JPEG file, proceed as is + os.write(0xFF); + os.write(EOI); + os.write(0xFF); + os.write(SOI); + if (val != -1) { + os.write(val); + } + if (val2 != -1) { + os.write(val2); + } + while ((val = is.read()) > -1) { + os.write(val); + } + return; + } + } else { + //not a JPEG file, proceed as is + os.write(0xFF); + os.write(EOI); + if (val != -1) { + os.write(val); + } + if (val2 != -1) { + os.write(val2); + } + while ((val = is.read()) > -1) { + os.write(val); + } + return; + } + } + os.write(0xFF); + os.write(SOI); } else { //not a JPEG file, proceed as is os.write(val); @@ -63,67 +89,39 @@ public class JpegFixer { } return; } + + //main removing EOI+SOI while ((val = is.read()) > -1) { if (val == 0xFF) { val = is.read(); if (val == 0) { - baos.write(0xff); - baos.write(val); + os.write(0xFF); + os.write(val); + prevEoi = false; continue; } - } else { - baos.write(val); - continue; - } - if (lastMarker > -1) { - data.add(baos.toByteArray()); - markers.add(lastMarker); - baos = new ByteArrayOutputStream(); - } - lastMarker = val; - } - if (lastMarker > -1) { - data.add(baos.toByteArray()); - markers.add(lastMarker); - } - boolean wasApp0 = false; - for (int i = 0; i < data.size(); i++) { - if (markers.get(i) == APP0) { - wasApp0 = true; - } - if (i > 0 && markers.get(i) == SOI && markers.get(i - 1) == EOI && !wasApp0) { - markers.remove(i); - data.remove(i); - markers.remove(i - 1); - data.remove(i - 1); - i--; - List dataToMove = new ArrayList<>(); - List markersToMove = new ArrayList<>(); - for (int j = i; j < data.size(); j++) { - //move these data up - if (markers.get(j) == APP0 || Arrays.asList(SOF0, SOF1, SOF2, SOF3, SOF5, SOF6, SOF7, SOF9, SOF10, SOF11, SOF13, SOF14, SOF15).contains(markers.get(j))) { - markersToMove.add(markers.get(j)); - dataToMove.add(data.get(j)); - data.remove(j); - markers.remove(j); - j--; - } else { - break; - } + if (val == SOI && prevEoi) { + //ignore, effectively removing EOI and SOI + } else if (prevEoi) { + os.write(0xFF); + os.write(EOI); + os.write(0xFF); + os.write(val); + } else if (val != EOI) { + os.write(0xFF); + os.write(val); } - data.addAll(1, dataToMove); - markers.addAll(1, markersToMove); - break; + + prevEoi = val == EOI; + } else { + os.write(val); + prevEoi = false; } } - - os.write(0xFF); - os.write(SOI); - for (int i = 0; i < data.size(); i++) { + if (prevEoi) { os.write(0xFF); - os.write(markers.get(i)); - os.write(data.get(i)); + os.write(EOI); } } } diff --git a/libsrc/ffdec_lib/test/com/jpexs/helpers/JpegFixerTest.java b/libsrc/ffdec_lib/test/com/jpexs/helpers/JpegFixerTest.java new file mode 100644 index 000000000..fd8942451 --- /dev/null +++ b/libsrc/ffdec_lib/test/com/jpexs/helpers/JpegFixerTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2010-2021 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.helpers; + +import static com.jpexs.helpers.JpegFixer.EOI; +import static com.jpexs.helpers.JpegFixer.SOI; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * + * @author JPEXS + */ +public class JpegFixerTest { + + private JpegFixer fixer = new JpegFixer(); + + @DataProvider(name = "bytes") + public static Object[][] provideSamples() { + return new Object[][]{ + { + new byte[]{(byte)0xFF, (byte)EOI, (byte)0xFF, (byte)SOI, (byte)0xFF, (byte)SOI, (byte) 0x21, (byte)0xFF, (byte)EOI}, + new byte[]{(byte)0xFF, (byte)SOI, (byte) 0x21, (byte)0xFF, (byte)EOI}, + }, + { + new byte[]{(byte)0xFF, (byte)EOI, (byte)0xFF, (byte)SOI}, + new byte[]{(byte)0xFF, (byte)EOI, (byte)0xFF, (byte)SOI}, + }, + { + new byte[]{(byte)0xFF, (byte)EOI, (byte)0xFF, (byte)SOI, 0x23}, + new byte[]{(byte)0xFF, (byte)EOI, (byte)0xFF, (byte)SOI, 0x23}, + }, + { + new byte[]{(byte)0xFF, (byte)EOI}, + new byte[]{(byte)0xFF, (byte)EOI}, + }, + { + new byte[]{(byte)0xFF}, + new byte[]{(byte)0xFF}, + }, + { + new byte[]{(byte)0x26}, + new byte[]{(byte)0x26}, + }, + { + new byte[]{}, + new byte[]{}, + }, + { + new byte[]{(byte)0xFF, (byte)SOI, 0x27, 0x37, 0x47, 0x57, (byte)0xFF, (byte)EOI}, + new byte[]{(byte)0xFF, (byte)SOI, 0x27, 0x37, 0x47, 0x57, (byte)0xFF, (byte)EOI} + }, + { + new byte[]{(byte)0xFF, (byte)SOI, 0x28, 0x38, (byte)0xFF, (byte)EOI, (byte)0xFF, (byte)SOI, 0x48, 0x58, (byte)0xFF, (byte)EOI}, + new byte[]{(byte)0xFF, (byte)SOI, 0x28, 0x38, 0x48, 0x58, (byte)0xFF, (byte)EOI} + } + }; + } + + @Test(dataProvider = "bytes") + public void testFixJpeg(byte[] inputData, byte[] expectedOutput) throws IOException { + JpegFixer fixer = new JpegFixer(); + ByteArrayInputStream bais = new ByteArrayInputStream(inputData); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + fixer.fixJpeg(bais, baos); + byte[] actualOutput = baos.toByteArray(); + try { + Assert.assertEquals(Helper.byteArrToString(actualOutput), Helper.byteArrToString(expectedOutput), "Bytes do not match"); + } catch (AssertionError er) { + System.out.println("Bytes do not match:"); + System.out.println("INPUT: " + Helper.byteArrToString(inputData)); + System.out.println("ACTUAL OUTPUT: " + Helper.byteArrToString(actualOutput)); + System.out.println("EXPECTED OUTPUT: " + Helper.byteArrToString(expectedOutput)); + Assert.fail(); + } + } +}