TMI-101, 102 & 103: Now better handles broken data (throws IIOException) and better detects bogus Adobe markers.

This commit is contained in:
Harald Kuhr
2015-03-02 10:24:21 +01:00
parent d8f5293f94
commit 1f99040927
5 changed files with 267 additions and 27 deletions
@@ -319,6 +319,19 @@ public class JPEGImageReader extends ImageReaderBase {
ICC_Profile profile = getEmbeddedICCProfile(false); ICC_Profile profile = getEmbeddedICCProfile(false);
AdobeDCTSegment adobeDCT = getAdobeDCT(); AdobeDCTSegment adobeDCT = getAdobeDCT();
SOFSegment sof = getSOF(); SOFSegment sof = getSOF();
if (adobeDCT != null && (adobeDCT.getTransform() == AdobeDCTSegment.YCC && sof.componentsInFrame() != 3 ||
adobeDCT.getTransform() == AdobeDCTSegment.YCCK && sof.componentsInFrame() != 4)) {
processWarningOccurred(String.format(
"Invalid Adobe App14 marker. Indicates %s data, but SOF%d has %d color components. " +
"Ignoring Adobe App14 marker.",
adobeDCT.getTransform() == AdobeDCTSegment.YCCK ? "YCCK/CMYK" : "YCC/RGB",
sof.marker & 0xf, sof.componentsInFrame()
));
adobeDCT = null;
}
JPEGColorSpace sourceCSType = getSourceCSType(getJFIF(), adobeDCT, sof); JPEGColorSpace sourceCSType = getSourceCSType(getJFIF(), adobeDCT, sof);
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is) // We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
@@ -335,7 +348,7 @@ public class JPEGImageReader extends ImageReaderBase {
} }
// TODO: Possible to optimize slightly, to avoid readAsRaster for non-CMyK and other good types? // TODO: Possible to optimize slightly, to avoid readAsRaster for non-CMyK and other good types?
return readImageAsRasterAndReplaceColorProfile(imageIndex, param, sof, sourceCSType, adobeDCT, ensureDisplayProfile(profile)); return readImageAsRasterAndReplaceColorProfile(imageIndex, param, sof, sourceCSType, ensureDisplayProfile(profile));
} }
if (DEBUG) { if (DEBUG) {
@@ -345,7 +358,7 @@ public class JPEGImageReader extends ImageReaderBase {
return delegate.read(imageIndex, param); return delegate.read(imageIndex, param);
} }
private BufferedImage readImageAsRasterAndReplaceColorProfile(int imageIndex, ImageReadParam param, SOFSegment startOfFrame, JPEGColorSpace csType, AdobeDCTSegment adobeDCT, ICC_Profile profile) throws IOException { private BufferedImage readImageAsRasterAndReplaceColorProfile(int imageIndex, ImageReadParam param, SOFSegment startOfFrame, JPEGColorSpace csType, ICC_Profile profile) throws IOException {
int origWidth = getWidth(imageIndex); int origWidth = getWidth(imageIndex);
int origHeight = getHeight(imageIndex); int origHeight = getHeight(imageIndex);
@@ -366,16 +379,6 @@ public class JPEGImageReader extends ImageReaderBase {
else if (intendedCS != null) { else if (intendedCS != null) {
// Handle inconsistencies // Handle inconsistencies
if (startOfFrame.componentsInFrame() != intendedCS.getNumComponents()) { if (startOfFrame.componentsInFrame() != intendedCS.getNumComponents()) {
if (startOfFrame.componentsInFrame() < 4 && (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK)) {
processWarningOccurred(String.format(
"Invalid Adobe App14 marker. Indicates YCCK/CMYK data, but SOF%d has %d color components. " +
"Ignoring Adobe App14 marker, assuming YCbCr/RGB data.",
startOfFrame.marker & 0xf, startOfFrame.componentsInFrame()
));
csType = JPEGColorSpace.YCbCr;
}
else {
// If ICC profile number of components and startOfFrame does not match, ignore ICC profile // If ICC profile number of components and startOfFrame does not match, ignore ICC profile
processWarningOccurred(String.format( processWarningOccurred(String.format(
"Embedded ICC color profile is incompatible with image data. " + "Embedded ICC color profile is incompatible with image data. " +
@@ -388,7 +391,6 @@ public class JPEGImageReader extends ImageReaderBase {
convert = new ColorConvertOp(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), image.getColorModel().getColorSpace(), null); convert = new ColorConvertOp(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), image.getColorModel().getColorSpace(), null);
} }
} }
}
// NOTE: Avoid using CCOp if same color space, as it's more compatible that way // NOTE: Avoid using CCOp if same color space, as it's more compatible that way
else if (intendedCS != image.getColorModel().getColorSpace()) { else if (intendedCS != image.getColorModel().getColorSpace()) {
if (DEBUG) { if (DEBUG) {
@@ -510,6 +512,10 @@ public class JPEGImageReader extends ImageReaderBase {
} }
static JPEGColorSpace getSourceCSType(JFIFSegment jfif, AdobeDCTSegment adobeDCT, final SOFSegment startOfFrame) throws IIOException { static JPEGColorSpace getSourceCSType(JFIFSegment jfif, AdobeDCTSegment adobeDCT, final SOFSegment startOfFrame) throws IIOException {
if (startOfFrame == null) {
throw new IIOException("No SOF segment in stream");
}
/* /*
ADAPTED from http://download.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html: ADAPTED from http://download.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html:
@@ -554,9 +560,15 @@ public class JPEGImageReader extends ImageReaderBase {
switch (adobeDCT.getTransform()) { switch (adobeDCT.getTransform()) {
case AdobeDCTSegment.YCC: case AdobeDCTSegment.YCC:
// TODO: Verify that startOfFrame has 3 components, otherwise issue warning and ignore adobeDCT // TODO: Verify that startOfFrame has 3 components, otherwise issue warning and ignore adobeDCT
if (startOfFrame.components.length != 3) {
break;
}
return JPEGColorSpace.YCbCr; return JPEGColorSpace.YCbCr;
case AdobeDCTSegment.YCCK: case AdobeDCTSegment.YCCK:
// TODO: Verify that startOfFrame has 4 components, otherwise issue warning and ignore adobeDCT // TODO: Verify that startOfFrame has 4 components, otherwise issue warning and ignore adobeDCT
if (startOfFrame.components.length != 4) {
break;
}
return JPEGColorSpace.YCCK; return JPEGColorSpace.YCCK;
case AdobeDCTSegment.Unknown: case AdobeDCTSegment.Unknown:
if (startOfFrame.components.length == 1) { if (startOfFrame.components.length == 1) {
@@ -1052,7 +1064,19 @@ public class JPEGImageReader extends ImageReaderBase {
@Override @Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException { public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
IIOMetadata imageMetadata = delegate.getImageMetadata(imageIndex); // TMI-101: As we catch the IndexOutOfBoundsException below, we need to make sure that we don't catch the
// IndexOutOfBoundsException that should be thrown, if (imageIndex < 0 || imageIndex > numImages).
checkBounds(imageIndex);
IIOMetadata imageMetadata;
try {
imageMetadata = delegate.getImageMetadata(imageIndex);
}
catch (IndexOutOfBoundsException knownIssue) {
// TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data.
throw new IIOException("Corrupt JPEG data: Bad segment offset/length", knownIssue);
}
if (imageMetadata != null && Arrays.asList(imageMetadata.getMetadataFormatNames()).contains(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0)) { if (imageMetadata != null && Arrays.asList(imageMetadata.getMetadataFormatNames()).contains(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0)) {
if (metadataCleaner == null) { if (metadataCleaner == null) {
@@ -1369,9 +1393,16 @@ public class JPEGImageReader extends ImageReaderBase {
// int sub = 4; // int sub = 4;
// param.setSourceSubsampling(sub, sub, 0, 0); // param.setSourceSubsampling(sub, sub, 0, 0);
// } // }
BufferedImage image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
param.setDestination(image);
// long start = System.currentTimeMillis(); // long start = System.currentTimeMillis();
BufferedImage image = reader.read(0, param); try {
image = reader.read(0, param);
}
catch (IOException e) {
e.printStackTrace();
}
// System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms"); // System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
// System.err.println("image: " + image); // System.err.println("image: " + image);
@@ -1380,8 +1411,6 @@ public class JPEGImageReader extends ImageReaderBase {
int maxW = 1280; int maxW = 1280;
int maxH = 800; int maxH = 800;
// int maxW = 400;
// int maxH = 400;
if (image.getWidth() > maxW || image.getHeight() > maxH) { if (image.getWidth() > maxW || image.getHeight() > maxH) {
// start = System.currentTimeMillis(); // start = System.currentTimeMillis();
float aspect = reader.getAspectRatio(0); float aspect = reader.getAspectRatio(0);
@@ -94,6 +94,16 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
// More test data in specific tests below // More test data in specific tests below
} }
protected List<TestData> getBrokenTestData() {
return Arrays.asList(
new TestData(getClassLoaderResource("/jpeg/broken-bogus-segment-length.jpg"), new Dimension(467, 612)),
new TestData(getClassLoaderResource("/jpeg/broken-adobe-marker-bad-length.jpg"), new Dimension(1800, 1200)),
new TestData(getClassLoaderResource("/jpeg/broken-invalid-adobe-ycc-gray.jpg"), new Dimension(11, 440))
);
// More test data in specific tests below
}
@Override @Override
protected ImageReaderSpi createProvider() { protected ImageReaderSpi createProvider() {
return SPI; return SPI;
@@ -398,6 +408,119 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
reader.dispose(); reader.dispose();
} }
@Test
public void testBrokenRead() throws IOException {
JPEGImageReader reader = createReader();
try {
for (TestData broken : getBrokenTestData()) {
reader.setInput(ImageIO.createImageInputStream(broken.getInput()));
try {
reader.read(0);
}
catch (IIOException expected) {
assertNotNull(expected.getMessage());
}
}
}
finally {
reader.dispose();
}
}
@Test
public void testBrokenGetDimensions() throws IOException {
JPEGImageReader reader = createReader();
try {
for (TestData broken : getBrokenTestData()) {
reader.setInput(ImageIO.createImageInputStream(broken.getInput()));
Dimension exptectedSize = broken.getDimension(0);
try {
assertEquals(exptectedSize.width, reader.getWidth(0));
assertEquals(exptectedSize.height, reader.getHeight(0));
}
catch (IIOException expected) {
assertNotNull(expected.getMessage());
}
}
}
finally {
reader.dispose();
}
}
@Test
public void testBrokenGetImageMetadata() throws IOException {
JPEGImageReader reader = createReader();
try {
for (TestData broken : getBrokenTestData()) {
reader.setInput(ImageIO.createImageInputStream(broken.getInput()));
try {
reader.getImageMetadata(0);
}
catch (IIOException expected) {
assertNotNull(expected.getMessage());
}
}
}
finally {
reader.dispose();
}
}
@Test(expected = IndexOutOfBoundsException.class)
public void testGetImageMetadataOutOfBounds() throws IOException {
JPEGImageReader reader = createReader();
try {
// Any sample should do here
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/gray-sample.jpg")));
reader.getImageMetadata(-1);
}
finally {
reader.dispose();
}
}
@Test(expected = IIOException.class)
public void testBrokenBogusSegmentLengthReadWithDestination() throws IOException {
JPEGImageReader reader = createReader();
try {
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/broken-bogus-segment-length.jpg")));
assertEquals(467, reader.getWidth(0));
assertEquals(612, reader.getHeight(0));
ImageTypeSpecifier type = reader.getImageTypes(0).next();
BufferedImage image = type.createBufferedImage(reader.getWidth(0), reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setDestination(image);
try {
reader.read(0, param);
}
catch (IOException e) {
// Even if we get an exception here, the image should contain 10-15% of the image
assertEquals(0xffffffff, image.getRGB(0, 0)); // white area
assertEquals(0xff0000ff, image.getRGB(67, 22)); // blue area
assertEquals(0xffff00ff, image.getRGB(83, 22)); // purple area
throw e;
}
}
finally {
reader.dispose();
}
}
@Test @Test
public void testHasThumbnailNoIFD1() throws IOException { public void testHasThumbnailNoIFD1() throws IOException {
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
@@ -0,0 +1,88 @@
ÿØÿî Adobe d€ ÿÛ „   



 
 #"""#'''''''''' 



!! !!''''''''''ÿÀ &å" ÿÄ¢   
   
 s !1AQa"q2‘¡±B#ÁRÑá3bð$r‚ñ%C4S’¢²csÂ5D'“£³6TdtÃÒâ
„”EF¤´VÓU(òãóÄÔäôeu…•¥µÅÕåõfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø)9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêú m !1AQa"q2¡±ðÁÑá#BRbrñ3$4C‚S%¢c²ÂsÒ5âDƒT“
&6E'dtU7ò£³Ã()Óã󄔤´ÄÔäôeu…•¥µÅÕåõFVfv†–¦¶ÆÖæöGWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêúÿÚ   ? âµZoŒ,ÄËeuß"ãp0DS=p¸‹¡ÅS5¸-Ôâ©!# ã;ïƒ âM1B¡϶,ˆ Ëà m˜’£aŠU8ñÅ ”)Àà–Û Jb©ô"§lNiÂôÀÉEÛ‰ Å^­å™SYò}͈þòŠƒÀŠg!ÔU­o$‰¶*Äd¯È~jƒF½hn~(.ìpëÎþL†êÓ:AC çUþ9Ÿ8Œ°ÛøªQþ·ñE¤îØûžwo~ðìßT½¼”‘ $V=93n>ìâs™#s؃Lèzåàò¿–´Mûë±õû§Ëlƒè ‘Œ±Ï`ú2—0C×õ*ÇÌ6Æh(d"¥?¦F´+Y|»ª½À?R»¬n@NÙò·ÞEi?êP\éÞc· ·lùË$%ŒŸT%¿"ñÌ
MX—ˆ¤nISÚ‡"ÐÎCvïÌmê¹"·º”øÓ쟻8*ËÂCpi”fá”s;ëÖÊ=bôï&êe%U'¸Î¯Bš†ƒê0©N‡ØŒâX¸ãv‚½ÅsºZ®hr»±Zò̬‡ly<ÃXþ(¾pó^…˨ñÂdm·9=ó‹q6¤bIf  nIÃÝòòÎ(ÄÚ¼¥M*`ˆoöGÔs[C-Ž¢Üyk#ŒpDqÈs€÷’ò‘es1øâ£A¿oØ9Ý-4ï-ißZl²‘ûrÄì>æ^?†:ç̶P7 {AÄuøB}¦bú¹Õ8y{RQ,¸±÷ '•59ER&o’“‹§’õ‡Ùmä'Ùÿ î¶ÞdÒZÝdyDlFñž£èâ´õ~ž-OÕ‘âó_ÏH€N¯¿ ?KÂfò^³«ÛÈ£Ý
žRÔä4X˜Ÿ`Nz=G¸<Vu¯ù_üJ˜1ÚMã=z¸ÆË8êsKû½L%îˆ?¥óœžMÕcZ´.>jGðÂËú
òŒšg§¤·¯C\k66«W¹mƒqïJ6HˬÕa¥ÎÇÚùá­']¸»(E ûJFuÙôÝ2¿©'±R?Ž—H±}š!óÊF<ÇÉ¢>ÐÆêXÀ¼ÑWlŽ™1¹òÕ«‚bøNË Ý[=@ä¾8Æ`òsôý­¥Í·î–ÈXôÁ¶öÍNh­H ´8e» ›°`ØP:p#n˜
{1H½&UÂëèZ•¥ tßlMÐ`‰Ã°$ņء¥ QAÀ…›\ƒ\R›Dâ¸-Z‹„ÑOA‹­çlUÒŽùGâÀ«*±ÅÕûb­ŸlgÃE,ô!à(µ!ã ›ûbʇ³;⨲¥p;€7Æúã04’žÇDß4’Ž;`5”“¾g”ôÅ*r=[;ã]¾,µ5Å\+\YV¸™W bª¼E3ezƒŽlU,åSˆãÔÓD¥1E  ÏÃ/™ÅQÜÆØ¼rñß ¯µ1Tî;¥¦ç©A†¸-f¢ÓMRAËlŒ„o„KpF Šèž¸ªlÒé€ï. ]± .ˆÀ²Ì[SH¯PhAÎãùsxú§–/ìårì«ÈkÓo㜠žG;/äÙ?W¿ýôÙ“¦&²€ |AkŸ0~3×aú¾©4gj9ýy'üÌV6¾]¸ ˜šÁH>ÍGjá[޵qþ±É”¯ ó~qä½EÀ¼ˆ´‰\ïϼU?Í–JŽL¸ùqn>H¢{˜ÒA *Ù׿.õ§3F$zšŽùÆ5++Í*ö[ØÌSÂÅYXS¦I<«­Ki2p­AtÓ$KCîµÉQ|€æ_Kù‚Ô]iwüEVxD€ÿ ”½^y¦ÿ D»µ¹wàyNzËä—–<n£gÇiãá\ ªiI/w< è»új*Hù.ù1,y=]Õ»V\Ñㄇ/ƒÉ<§åÍbæd’Y”ŠC²³·hñM¦@¿X3ºŽŸyÈ›yÆÞÞÚY•>RBP âù½ÞàްPr
³W¾bKU#*`êGiá‰2c9ÈDˆr}tÚÔ91n@[}üjáœkŠªœ÷YŽÖ玧bõW4š>áüi†Ú_šmÖÙ!»ä®€/?´
;åFfRõ=/¹¯¾4ã”B ïŽRe–´ lº¶±¼¹·I=Èûò?wæÛdÚ/îßÄ ól
ýôEkЩ
ý0Xï6s×i¤x%!!æ.?sWžMŠgf¶¹1¡èŒ
G· pµü…v(o¾–K-/à¾ÔªÄˆÁøô8(Ói¤8 6—óNÌóI¿ÒÀkTí*î1k?8\D1´rqÛâS_ÀŒ3ó&¢ 4 ïÉÈ5éÛ#údZm¥Á–âÜJðú2<W¿“­‘†óŽ,’!ÅÒúÝt –ßÎDõˆ+âÈþ¸8ëú=Ü|e`ê’)þ„c ²Ò®¡Y"·‰‘†Ä «"^eÒZÎp¶ŽÈ®9 ðÞ…k†ä9¹sÉ«Å)d†XKk"ùòåHýLiRV®¢¿Èv¯ÈÓ ™xô5?HÐo.¢Y.[PÝÏË n4-ã.$&ƒ¾ èï$Û=>iñfŽ1óôúG¼Çq¤(qIB«à no$û=ʸHCF8š4KA?Tx¯\zZBã÷, ³ÐýØ;õmÎÇðÅÄ‘HAVâã}LNQæìtÚíV›måÐîˆÐÃ8>+ÿ ºœÐþÌž?<eÅ»õê<rè̤Òkñj"(ԺıۋE© ß
ní)Ó%O Uy9'14H)‡W;a\°š×B©#›œ¾íŠGN*­7Z`Øãf ¦-kÃ(mÖ•CÅnÊS=8ÐŒuªU:Ò§¶Îôc‡w´ ŒŽÜÈâ« ›×7©^¸ƒŒ©ÅQ!…s;`R™FJ⪛W7*b<órÅU‹æ
ˆ“˜6øª"¦™±>B™±T>^YCo,ªeUP66»ã+—\Uà”zázµ1tŒUàxؾ.\P½ªN6A¶)É:ânàŒU
½I Î÷ä
=<»åKZàq’t*•ðês‚¬¾ŒÉ ߉™èMâ/7yêñšMlœJ®Ûv;f^˜ yÊ"^QþÖ¹óã^÷…yQ†­<ácÓ
»žÎâ;g1Í Œ»F
ÖìeÓµ ­äÆ•Å4]çU¹H£B܈ ¹')Ï) ²‘؃ý‹,ÇŠG`ŒÐÚþhhF̺rZ‚ŸXz·» :òŸ-tØ’óU@Î!‡úÿ Ó¼«å m•€k¶v½U|~xs¬Ú]]X¼6;JÔiU®ùV\¼|&¨½:Ìù'f¨‚F;ú»¬w¢á½·XÓE‹e@´ ð˜5eG×9ãyg\U? ßˆqŠYkz–›p¶·àð_…ƒˆ÷ʸ·­÷ïpáÚ3Q†X %ÒþÆ]}¢i·Õi¡^gö×fûÆDï|¡t²ÒÎ@ñÕÏ>{o’–Õ¬¡Œ<󪎴¨Àñë¶7s‹{w.ç 
¦æê1hò‘Æc“µ$oïHÉÓˆ [Áë/‡ï¯ðÈF±eªéLÌ…¹'÷ˆGãòÎÇ4ñÚÂÓLx¢Š’r-5áóæÎeXÆæwܪøíãჄЏåþLŽ.?w½ç#WÖœ%¼,î>ÐQ†ƒË^aaq$/Åv
¿ÈgO°Òìô¸6‘„Y©»€µ‹BÝ)ene$nà‡ý‰Ã_̘1™÷ÓñUôHt•¾²>´¨ÑBGÆoî;Qó ¿Ââ4é˾FõÍgZ©=»ÔšŽdÓ
m<· Þët¾ÔX¥¸?
‹|½°Ý ä*Ë… Y¸81ÈÆò°fOÃGéÚuÖ¬åÐñŽ¿ÏZ}8>ïË3Á©o/®Ë»''ýŽçÚîÃKT´Z! ðƾM¨ë·%·>š
ýð v !̧&-&pä&yÞ“¸?wÍ-²Õ®t²h~
üHÝ+ƒ.|ánÀb‡’ý’ç‘,q4à›ˆÀ‘¾Ó§ÃËýa¸Â–Ñ,‹kO
°Æ2«иøõ Àå˜p÷òLÎ/s2Á´f4ýØ }ç
RâHLR¿*õ'­0"ÎÞOZ*‡;ÿ -Ö52P«eaU&¸ñN2ŽÜ~†U=FAO9FüR«ïOd ]± †Eâè­;T½Ôƒbæ´Á"ßPIƒz¼’»îVU-äNÑò
g±HÂs”z^ÿ bÛ5€ñ>¦]Mwhô ŠdÇ_YÅrŸÜwÆ3è[´Ú®”qÄ÷óJtíQ§$Zö®E$±(*yFz£tþÌ*µŽÖÕèFþ'
VDqU;a‘£`#>Aœxb`9Úù I£õ`ÿ d½Á þH
FøuÌÄüÐÐ÷ð#ß½‚;´,ƒ‹Êÿ L²/bîû?µ„„qçØòïc⸢¸*â3;SaEË]ÝÚ
H”
Ó‚¶/#Óá8ZŒU7´5…׎G-æEpÖ+¥ ¾)VšvWÛ¦ZÎ
ï…×
B”®*¾òNDÓ ec\$¢½pÌ ¨ÅP®)ˆ1Å]±¾*Ñ9Yy±V³W6lUºæÊx«uÚ™³fÅW‘\°òÀÌN*°ãN8åb«lqTÅ\1êq˜àqTLrÓ7 ˜“Š£R{æi
02¿l¶lU¶rs·þI$‚ÊþvbPG@½·9ÃUYØ"Š“¶z3òëO/”}Gøf¼4½ þÜ¿ ŽIó-9§ÕžV~‚ê¾V¼ó7™n Ö4cɺ($ä£J²Ñ|¥(…¹KxÇ ]’£¢‘Gykap–0¥%›â4‰¦ËolèZhQÙÅ*Ê 9šg$Ìù_+î:l‡&k1Ê8¡.D\cå]þiU¿˜MýÊÛY[³ŽîO£ÄõÉ«*¨­
~#×#¯y¥ù~(õ[~ ö˜ûœa¨ÜKgõËÅVUðNÄ× º|¤ ¹òW˜ëLgŽWä¯'ÏFO…½÷Â/0Yý~Ù-íT<ÈâŒiP:Ÿ
ºÕÞ³}õKI= p y«?”8·±[8 6`! ïSVñoÄΘÎ0ÀÜLïrGHÒ‘ÙùN£ê—nñ§Â¿S†¢çBÑ…hãaÕSw?>­o7§šly¼rI,Gv™9qùPl3›¾¥©K0I¤rkÒ§ ˜é°í/1˜Ý™ž)ü¿k×õ¯0þS°+ ûE¾ÑÁ¾W»±HM¿Ù¹$–¯íxSä0ƒBòÕÍÕ’L÷A ý‚9øQ‰ÞYÞi3ÖBj»£#껫¹×œ™ñæ©­¨÷wy=IT¹û#r}°üÏ¥‡1ó$ŽÁzü°ºËÌÉqŠíCFGqÔý
îÓB»`ËÊ:õ¥ãøäÀ'é¢äæ×Ø£ç m+÷×ú5äEf)2ªË_ÀŒjê6±F#µâ¨‚Šª:–FšÆÚŠÞ踔ãjF[ V.@Šp²vŽQ.Xî¹ÇŸÎËw×{£)55댔‚´®i"U£òͽoc\
#…ÜÐ
ÉÊç30G×ÈwN¹Jɳv}å+¿ÔÛM” #/}’0e•Ñ»ˆKLj=0ÖÓd¤Qš†ïþg
R4‰B
ª( è2‚rß«“›ÃŽ8Ç‚²ÿ ¾>+û`;ëÎ6SÈÔ`ºœ¬ïa¢”$%Dr!Nb‰B¢ì Å3WÌ=\ŠîMÉB^ÞIfUøòˆì}Ž>;ˆ/b"6ë۸ř#™
° ÔaLº{Ù?¯lIJî;Œ˜£·"ߌc”xO¢c‘è}ê÷6Kéе[±Âد$µ“Ó~ž8p¤Ïbj{á6©°Ø®ã'âäéÏ8²ogäS%¸õ0 Ž£"ÐjÍÀÛá…®ª$`욳ò3R¶.È6=pžO„Päš)ÒUàÛ©Œ Õàú¼„ðž™d%{;^ËÖqÄiòýq±èN—áéŽ,k\Bg©ß&íœ$>8¨¹jR¸ÉLa£dž½ñ=;àFœacŠ¢šrO\M¤Ä9f®*¹š¸ÊæÊÅ]—•›vlÙ±VÆ8 hÅR˜«\sb”ÍŠ¹‡†Q]±Í”tÅT÷Ë \q׸ªÑséŠôßf®*¢Â™YlwÊÅW˜ŒªÓ1jâ­c…XÐu8ÚáÆ¦µíÊ’>pBÚóeŽ,rÉ3´E§~TÐYVêðIMiÜû ì6³J­of´ê Aöa„tQî|rIk|!Féwíe+ÎÀÈJñ Ÿ‘ÈÃ'¢¹Ù÷‘ŸhÏ. Ï!1‰ mÒ(íjé-õdxZ%PÞÄi÷`[Ÿ2^ÊJ,ØSíá‘ýNîòk‚‘ÞÎK4ž;â–vâØ‘½Y;±þYÈH C[ÓFL“yÌYDóó,‡JÒe½‘o/÷UåFÝžŸÃ$:²É.™<p hµãì:Ó!ߤîÂúk+*vPiƒm5Éb¢œ”©ß}þu /i|ú¶`Ôá†9b1”|H)ó6BÆò[4[°Ú†¿Ã&vZå½Ü`òâß´§·Ó*Ôš˜àÄt9Î…^MZ}^\#¼O8— O¨[ ey
Œëºf‡ysíšzs†
PšoÓÍ8KÈÛbV×à3DñÂg¶Ãâ[²ësd‰ˆ€X翚¤jkc¬„ñ&ª1MGZ¶½€Å$F£ì½FÇÇ#W7ÐÛ¡f5§a‘-G]žæB‘±ô ïŒxˆØVÌ´˜µYááDŒv:^©©Ï¦_Ÿª¸hœ
7ã[ù¶2 ž"§¹Sýqm3G³–Ú9î“Ô–AÈ«€=69´M)Å
²#øà2"
Ž­™3h ²âœåIœvºø­‹ÌzsïŠû08ºëV,*.Sé48O,iïR…ãö£ñ]?”¤ÜÁp€`GêÂ&:NCâÕ]3ýìñÿ X_Ü›\yNˆÞú‡ÁF]Hë2{`V>¦½~œM êqÌ#x˜ò!U†êkî2c£i?¢à!˜4OP·È2¡½ñ›‘¨Á¢ÓbÅ“ÄÉ/§{ý“+khí£
¿Iñ8ö;æ©Æ’ ©Êå0@ {Ý9²I&Éo6j~eŠÕÌ6ª%lXý‘ò¦Ò®î/-„÷‘øiµF—"zLÐÄ3N<1—+æ~i57³»0Î*•ëìza²:JÐòVèp&¥§¥ü$ÆUûü¶F´íNçOº6³Ê‹)ì}²T$6æá§Ž£²c¨÷ŽðÉdv²~G{v;ÿ pZºH¡†SÜb$WnŽ:d<ê×zMÓĨ
8ž”ÄG‹ÞƒI=H†Ù!Ðÿ ýlà*¨  }kÈAë„Ñy­\|iCíŽmx¿ÙS„B@¦:V9Ù‰‰R›Ež‰º±ë‡Vj ¹ø°7éE'“­ ´×Pž
<U³›Ÿ&¶xÄGðém~®E
ß"^@@ûiú°KÞ¤¢ âuˆQÆÍßà $n\<RÉ G!±8›õ°éÁŠ©€%}òI­Ø‚¿Y‡ìž£Ã"òÔeàØz½.¢9ñ Ž|ˆî*LÄœms•\.C‰ÊÍ›vlÙ±WfÍ—LU¬ÙtÍLUÔÍLx.˜ªÊc†Ç1 b®å›\تö>˜&¹‰ËN¸ª²-zã¸S2WR*q27Á-@1oЍ2q¼i)+Ѝ•‹'Àâ«­ iåX׹Γ¢éñØÚ«RŒG\ŠyvÈ4ž»…r\÷aUcNBNÁç{g<²LiñŸL~¯z=%M6¡=ñej<p‰o¹Üè ©Á×Wž•³H›ÉO„e`C¥žžBQ}H†’êìÁOrO†êcŽ c¶äñí‘É稜FiØŠž˜¶–lþµë\|j›ªíÚ¹!Œ
ÎîÓfCN\ד„Xˆä|“½2
FíÅíôŒ‘Õ:ôv6÷X·´øGÄÝ_k Ëû½‡`:d}ÅÕÅ´ŠÜIëCJxá³rù#ê'âgÇ¡¨w2³¨1µW? Éñ|é…Òj³'IË ]ݱøì6¦2¹8DCv h“¹ºG]j—R­É+ôÄ–öiˆ/Ä}ÎøLÿ f½Ž6f
+ò¸¤!@ß.«çÔg–ªÌhsXG÷($û òsì0âöŒT;ä«g.x£ RÇ
ŠÙžC}­(: Sô•¸L¹
[©QIµ–[‹ Kõ'å•ðé’âDäeB"ÙÛ^(éÓ·ñFÛß…ÃSkŠÖæéÀŒ…^ìÆŸÛƒ€uq3Â;ÓÓ{l:È.’«­Qƒ)ðß
¿@ÀÈD’·3ûKAýpžêÏVÒ˜É3Ä?m7Ûü¡Û" þE†=6 §ƒj—N1@û‹+’T‰KÈh£©ÈΫ®´õµ´û'béÂ[Ífötôå|4òͬDµíÍ(¿ÝòññÉ‹;¹ÐìøéqF¢¦côÄr'¢#Kòà%n¯¾bù«nf1-QP(×Ôm¸ θ¬l¬6£〓v\Ù³d˜ÉœÝ@&9uæ9cå{v¯†!¢Fo¯Zæˆ.ÿ N_i}Õ[‡¢ýÊý“ì–=Z4q¿Ú9 El7sã›åå< rLQ÷uÝ3Ò¡vðÕ4Ùï¦åU†Í‡"ò&;¾ü]5(TR‚¾;b,r
'O.<p¹y¤Ze¨{–⣶­–Ÿ ñ uÿ ´ ]sª"׉©ÇÔy³™Õêes‘QØ#®íìÝHT ûdfêµ—’¯c‹µõÄÆ‰_£*\H‡šš{ŒÙÎÓcžSƒÌj–÷Ì(á¤S™c¿lŒ«ðjaÝÓêt¢¸¢<Óˆä­û«þ"ú½“ZLŸ ܘÛfPHï€<Áh&´,¢­ßGlc*.6UájDyFf¤:_BÁÎV8Š2²×£k6l¼U¬Ù³b®Çª)8«@e¦,+Ьt¦<
ªb­Í\Q†$Ûb­W6VlUT!®*™{ Ä⫆Ù|é‰3Ó¦$X×E
˜
ð:¾øº8lUqL±®,€bª…ŽØªŠÁ·LÆÚ½°XZ z Hø “{ŵœj6-¹Åä•AÛsD‰òÿ JìñXtÊ^`ÇŽvyÊE¨ŸŒÏ/ŽÃ凷JJÀÈ› ùdb[ž"ƒ½Óžø˜Ût´RÌ9˜ù†]¨iºV …‹,rSgOâ2%=©±™ã²Ã¾'õ©Wì±ûð3ÎÎõcZáŒHÚíËÒi3b)œ?šz{ py
œÚŒŽž˜VkÛ-d©ÉžŸ¨6ä«,µ8Ô5ë‰È
2Ðü8¶‡Þ3L@’qkrø·C±® ¾²ú­ 5¨}8´Œ‘„Æ3Î[„µÅ` .øÞ'§·†-çÕ/0ƒ*Ö^Ì:œÇ.+\kf'à#½8Šâ†¬i†vú€P(vÈÉ‘²ÖY|.Mf7¦kÿ 3@pÂ)Õº÷È<²B¿Ç&°àõürërödÉ<fKy¢i×õb$?¶›}ã"z½Õ„æÐÀ}+B0Ê
{‡Ú4ÅŸY¶½dF‹›!ª1ê1ažœjôò©ÄåÆ#ü>b×èº8Š1uz;
¤mÐ{œ1¸£-#j7ìÐÐaί!%A¦¾¥-~Ö<$AÒê3ä9fw<‡@;‘·-­|Qˆäãâ?ˆÂ)于B²rVî
Aû
NEjÖƒÛÝ­ž¤²qYPu?µ’¹‡+s§ŸL1áë(ïÜÅc–sП£Þ‘P¯÷5ÂÛTE@G€Àë©\áø9|s˜â†(æ¢æâ1ñòQ5Ä¡+@Näá£Ü ¢*ûÔnÀ¾Œ¤øck%Â~µ2M­”@F ·w=q/QÑÞ¸Y$ìýN&à§:[õH“#½” ç÷¦ž8èØ‚1Ó çÈåŽ.ŽÒÇ ÚöL­u„€NØv$[»w^¼”þApHv8wb ¡« ]>®8Ǫ Æ@°{¨Ìs:‘Ðâ8yæ ?Bè¸ nÓ-ýÓåqC êÜÔË9†×S52éŽT'iWD•Æ,d`ˆÁb­²Pb.¤`$
ð4\UI‰Æ×1leqUäâg/(â­S6_lت ŒÕãÓ|Ucœes¡Š®Œâ@cÔŠ£âaMðTtꬄm‹,个:W
6;â)1ä)ãZRNhž²(÷ÅŒ¹s'ü·ùÙ'ŽKvŒŸˆn1Ó
Eú˜PìCœ©ÐbÆ'åÂoíR”îqX¤†¸‘ÉkŒl´œ
/!¸&ª»õ>øœŒÒ
Sn‰£Ëf¢¹ZQñít‹öp:YÜHi>¸¿è}C/Aéò8vY ú¤‘.K…sÄâ¾Ã IПJ‘Šà‚}ø×rL`45Åg`duË0?P*2-Âý[Þ§ËlL}ºxâ†7F"àƒ\-‘®U1ˆÜ~˜>ÎT”pq¾k4;¡ú0_BÕã3 ¯52ðñ¨ÜøcGò“ÆÃÅC‚šÞ£l e(Dðñú¡d”¹ð€Æ OL{ÄTãƒ²Ž S ` À˜ŠW4POцV–Ëm4’`|(7ûÎ õß1Fíá‰kÉ6":_#Õ‰ÀîÄš ³Ë)6`Ol[cßH¡°(fÚ»Ó+ë2

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B