TestMetadata.cpp (13405B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "gtest/gtest.h" 6 7 #include "Common.h" 8 #include "Decoder.h" 9 #include "DecoderFactory.h" 10 #include "decoders/nsBMPDecoder.h" 11 #include "IDecodingTask.h" 12 #include "imgIContainer.h" 13 #include "ImageFactory.h" 14 #include "mozilla/gfx/2D.h" 15 #include "nsComponentManagerUtils.h" 16 #include "nsCOMPtr.h" 17 #include "nsIInputStream.h" 18 #include "mozilla/RefPtr.h" 19 #include "nsStreamUtils.h" 20 #include "nsString.h" 21 #include "nsThreadUtils.h" 22 #include "ProgressTracker.h" 23 #include "SourceBuffer.h" 24 25 using namespace mozilla; 26 using namespace mozilla::gfx; 27 using namespace mozilla::image; 28 29 enum class BMPWithinICO { NO, YES }; 30 31 static void CheckMetadataFrameCount( 32 const ImageTestCase& aTestCase, 33 NotNull<RefPtr<SourceBuffer>>& aSourceBuffer, BMPWithinICO aBMPWithinICO) { 34 // Create a metadata decoder. 35 DecoderType decoderType = DecoderFactory::GetDecoderType(aTestCase.mMimeType); 36 DecoderFlags decoderFlags = 37 DecoderFactory::GetDefaultDecoderFlagsForType(decoderType); 38 decoderFlags |= DecoderFlags::COUNT_FRAMES; 39 RefPtr<image::Decoder> decoder = 40 DecoderFactory::CreateAnonymousMetadataDecoder(decoderType, aSourceBuffer, 41 decoderFlags); 42 ASSERT_TRUE(decoder != nullptr); 43 RefPtr<IDecodingTask> task = 44 new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ false); 45 46 if (aBMPWithinICO == BMPWithinICO::YES) { 47 static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO(); 48 } 49 50 // Run the metadata decoder synchronously. 51 task->Run(); 52 53 // Ensure that the metadata decoder didn't make progress it shouldn't have 54 // (which would indicate that it decoded past the header of the image). 55 Progress metadataProgress = decoder->TakeProgress(); 56 EXPECT_TRUE( 57 0 == (metadataProgress & 58 ~(FLAG_SIZE_AVAILABLE | FLAG_HAS_TRANSPARENCY | FLAG_IS_ANIMATED))); 59 60 // If the test case is corrupt, assert what we can and return early. 61 if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) { 62 EXPECT_TRUE(decoder->GetDecodeDone()); 63 EXPECT_TRUE(decoder->HasError()); 64 return; 65 } 66 67 EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError()); 68 69 // Check that we got the expected metadata. 70 EXPECT_TRUE(metadataProgress & FLAG_SIZE_AVAILABLE); 71 72 OrientedIntSize metadataSize = decoder->Size(); 73 EXPECT_EQ(aTestCase.mSize.width, metadataSize.width); 74 if (aBMPWithinICO == BMPWithinICO::YES) { 75 // Half the data is considered to be part of the AND mask if embedded 76 EXPECT_EQ(aTestCase.mSize.height / 2, metadataSize.height); 77 } else { 78 EXPECT_EQ(aTestCase.mSize.height, metadataSize.height); 79 } 80 81 bool expectTransparency = 82 aBMPWithinICO == BMPWithinICO::YES 83 ? true 84 : bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT); 85 EXPECT_EQ(expectTransparency, bool(metadataProgress & FLAG_HAS_TRANSPARENCY)); 86 87 EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED), 88 bool(metadataProgress & FLAG_IS_ANIMATED)); 89 90 EXPECT_TRUE(decoder->WantsFrameCount()); 91 const auto metadata = decoder->GetImageMetadata(); 92 ASSERT_TRUE(metadata.HasFrameCount()); 93 EXPECT_EQ(aTestCase.mFrameCount, metadata.GetFrameCount()); 94 } 95 96 static void CheckMetadataCommon(const ImageTestCase& aTestCase, 97 NotNull<RefPtr<SourceBuffer>>& aSourceBuffer, 98 BMPWithinICO aBMPWithinICO) { 99 // Create a metadata decoder. 100 DecoderType decoderType = DecoderFactory::GetDecoderType(aTestCase.mMimeType); 101 DecoderFlags decoderFlags = 102 DecoderFactory::GetDefaultDecoderFlagsForType(decoderType); 103 decoderFlags |= DecoderFlags::FIRST_FRAME_ONLY; 104 RefPtr<image::Decoder> decoder = 105 DecoderFactory::CreateAnonymousMetadataDecoder(decoderType, aSourceBuffer, 106 decoderFlags); 107 ASSERT_TRUE(decoder != nullptr); 108 RefPtr<IDecodingTask> task = 109 new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ false); 110 111 if (aBMPWithinICO == BMPWithinICO::YES) { 112 static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO(); 113 } 114 115 // Run the metadata decoder synchronously. 116 task->Run(); 117 118 // Ensure that the metadata decoder didn't make progress it shouldn't have 119 // (which would indicate that it decoded past the header of the image). 120 Progress metadataProgress = decoder->TakeProgress(); 121 EXPECT_TRUE( 122 0 == (metadataProgress & 123 ~(FLAG_SIZE_AVAILABLE | FLAG_HAS_TRANSPARENCY | FLAG_IS_ANIMATED))); 124 125 // If the test case is corrupt, assert what we can and return early. 126 if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) { 127 EXPECT_TRUE(decoder->GetDecodeDone()); 128 EXPECT_TRUE(decoder->HasError()); 129 return; 130 } 131 132 EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError()); 133 134 // Check that we got the expected metadata. 135 EXPECT_TRUE(metadataProgress & FLAG_SIZE_AVAILABLE); 136 137 OrientedIntSize metadataSize = decoder->Size(); 138 EXPECT_EQ(aTestCase.mSize.width, metadataSize.width); 139 if (aBMPWithinICO == BMPWithinICO::YES) { 140 // Half the data is considered to be part of the AND mask if embedded 141 EXPECT_EQ(aTestCase.mSize.height / 2, metadataSize.height); 142 } else { 143 EXPECT_EQ(aTestCase.mSize.height, metadataSize.height); 144 } 145 146 bool expectTransparency = 147 aBMPWithinICO == BMPWithinICO::YES 148 ? true 149 : bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT); 150 EXPECT_EQ(expectTransparency, bool(metadataProgress & FLAG_HAS_TRANSPARENCY)); 151 152 EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED), 153 bool(metadataProgress & FLAG_IS_ANIMATED)); 154 155 // Create a full decoder, so we can compare the result. 156 decoder = DecoderFactory::CreateAnonymousDecoder( 157 decoderType, aSourceBuffer, Nothing(), DecoderFlags::FIRST_FRAME_ONLY, 158 aTestCase.mSurfaceFlags); 159 ASSERT_TRUE(decoder != nullptr); 160 task = 161 new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ false); 162 163 if (aBMPWithinICO == BMPWithinICO::YES) { 164 static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO(); 165 } 166 167 // Run the full decoder synchronously. 168 task->Run(); 169 170 EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError()); 171 Progress fullProgress = decoder->TakeProgress(); 172 173 // If the metadata decoder set a progress bit, the full decoder should also 174 // have set the same bit. 175 EXPECT_EQ(fullProgress, metadataProgress | fullProgress); 176 177 // The full decoder and the metadata decoder should agree on the image's size. 178 OrientedIntSize fullSize = decoder->Size(); 179 EXPECT_EQ(metadataSize.width, fullSize.width); 180 EXPECT_EQ(metadataSize.height, fullSize.height); 181 182 // We should not discover transparency during the full decode that we didn't 183 // discover during the metadata decode, unless the image is animated. 184 EXPECT_TRUE(!(fullProgress & FLAG_HAS_TRANSPARENCY) || 185 (metadataProgress & FLAG_HAS_TRANSPARENCY) || 186 (fullProgress & FLAG_IS_ANIMATED)); 187 } 188 189 static void CheckMetadata(const ImageTestCase& aTestCase, 190 BMPWithinICO aBMPWithinICO = BMPWithinICO::NO, 191 bool aSkipCommon = false, 192 bool aSkipFrameCount = false) { 193 nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath); 194 ASSERT_TRUE(inputStream != nullptr); 195 196 // Figure out how much data we have. 197 uint64_t length; 198 nsresult rv = inputStream->Available(&length); 199 ASSERT_NS_SUCCEEDED(rv); 200 201 // Write the data into a SourceBuffer. 202 auto sourceBuffer = MakeNotNull<RefPtr<SourceBuffer>>(); 203 sourceBuffer->ExpectLength(length); 204 rv = sourceBuffer->AppendFromInputStream(inputStream, length); 205 ASSERT_NS_SUCCEEDED(rv); 206 sourceBuffer->Complete(NS_OK); 207 208 if (!aSkipCommon) { 209 CheckMetadataCommon(aTestCase, sourceBuffer, aBMPWithinICO); 210 } 211 212 if (!aSkipFrameCount) { 213 CheckMetadataFrameCount(aTestCase, sourceBuffer, aBMPWithinICO); 214 } 215 } 216 217 class ImageDecoderMetadata : public ::testing::Test { 218 protected: 219 AutoInitializeImageLib mInit; 220 }; 221 222 TEST_F(ImageDecoderMetadata, TransparentAVIF) { 223 CheckMetadata(TransparentAVIFTestCase()); 224 } 225 226 TEST_F(ImageDecoderMetadata, PNG) { CheckMetadata(GreenPNGTestCase()); } 227 TEST_F(ImageDecoderMetadata, TransparentPNG) { 228 CheckMetadata(TransparentPNGTestCase()); 229 } 230 TEST_F(ImageDecoderMetadata, GIF) { CheckMetadata(GreenGIFTestCase()); } 231 TEST_F(ImageDecoderMetadata, TransparentGIF) { 232 CheckMetadata(TransparentGIFTestCase()); 233 } 234 TEST_F(ImageDecoderMetadata, JPG) { CheckMetadata(GreenJPGTestCase()); } 235 TEST_F(ImageDecoderMetadata, BMP) { CheckMetadata(GreenBMPTestCase()); } 236 TEST_F(ImageDecoderMetadata, ICO) { CheckMetadata(GreenICOTestCase()); } 237 TEST_F(ImageDecoderMetadata, Icon) { CheckMetadata(GreenIconTestCase()); } 238 TEST_F(ImageDecoderMetadata, WebP) { CheckMetadata(GreenWebPTestCase()); } 239 TEST_F(ImageDecoderMetadata, AVIF) { CheckMetadata(GreenAVIFTestCase()); } 240 241 #ifdef MOZ_JXL 242 TEST_F(ImageDecoderMetadata, JXL) { CheckMetadata(GreenJXLTestCase()); } 243 TEST_F(ImageDecoderMetadata, TransparentJXL) { 244 CheckMetadata(TransparentJXLTestCase()); 245 } 246 #endif 247 248 TEST_F(ImageDecoderMetadata, AnimatedGIF) { 249 CheckMetadata(GreenFirstFrameAnimatedGIFTestCase()); 250 } 251 252 TEST_F(ImageDecoderMetadata, AnimatedPNG) { 253 CheckMetadata(GreenFirstFrameAnimatedPNGTestCase()); 254 } 255 256 TEST_F(ImageDecoderMetadata, AnimatedWebP) { 257 CheckMetadata(GreenFirstFrameAnimatedWebPTestCase()); 258 } 259 260 TEST_F(ImageDecoderMetadata, AnimatedAVIF) { 261 // TODO: If we request first frame only decoding, the AVIF decoder says the 262 // animated image is not animated. This should be fixed at some point. 263 CheckMetadata(GreenFirstFrameAnimatedAVIFTestCase(), BMPWithinICO::NO, 264 /* aSkipCommon */ true, /* aSkipFrameCount */ false); 265 } 266 267 TEST_F(ImageDecoderMetadata, FirstFramePaddingGIF) { 268 CheckMetadata(FirstFramePaddingGIFTestCase()); 269 } 270 271 TEST_F(ImageDecoderMetadata, TransparentIfWithinICOBMPNotWithinICO) { 272 CheckMetadata(TransparentIfWithinICOBMPTestCase(TEST_CASE_DEFAULT_FLAGS), 273 BMPWithinICO::NO); 274 } 275 276 TEST_F(ImageDecoderMetadata, TransparentIfWithinICOBMPWithinICO) { 277 CheckMetadata(TransparentIfWithinICOBMPTestCase(TEST_CASE_IS_TRANSPARENT), 278 BMPWithinICO::YES); 279 } 280 281 TEST_F(ImageDecoderMetadata, RLE4BMP) { CheckMetadata(RLE4BMPTestCase()); } 282 TEST_F(ImageDecoderMetadata, RLE8BMP) { CheckMetadata(RLE8BMPTestCase()); } 283 284 TEST_F(ImageDecoderMetadata, Corrupt) { CheckMetadata(CorruptTestCase()); } 285 286 TEST_F(ImageDecoderMetadata, NoFrameDelayGIF) { 287 // We skip the frame count version because we realize it is animated with a 288 // full decode, so the test isn't consistent. 289 CheckMetadata(NoFrameDelayGIFTestCase(), BMPWithinICO::NO, 290 /* aSkipCommon */ false, /* aSkipFrameCount */ true); 291 } 292 293 TEST_F(ImageDecoderMetadata, NoFrameDelayGIFFullDecode) { 294 ImageTestCase testCase = NoFrameDelayGIFTestCase(); 295 296 // The previous test (NoFrameDelayGIF) verifies that we *don't* detect that 297 // this test case is animated, because it has a zero frame delay for the first 298 // frame. This test verifies that when we do a full decode, we detect the 299 // animation at that point and successfully decode all the frames. 300 301 // Create an image. 302 RefPtr<Image> image = ImageFactory::CreateAnonymousImage( 303 nsDependentCString(testCase.mMimeType)); 304 ASSERT_TRUE(!image->HasError()); 305 306 nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath); 307 ASSERT_TRUE(inputStream != nullptr); 308 309 // Figure out how much data we have. 310 uint64_t length; 311 nsresult rv = inputStream->Available(&length); 312 ASSERT_NS_SUCCEEDED(rv); 313 314 // Write the data into the image. 315 rv = image->OnImageDataAvailable(nullptr, inputStream, 0, 316 static_cast<uint32_t>(length)); 317 ASSERT_NS_SUCCEEDED(rv); 318 319 // Let the image know we've sent all the data. 320 rv = image->OnImageDataComplete(nullptr, NS_OK, true); 321 ASSERT_NS_SUCCEEDED(rv); 322 323 RefPtr<ProgressTracker> tracker = image->GetProgressTracker(); 324 tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE); 325 326 // Use GetFrame() to force a sync decode of the image. 327 RefPtr<SourceSurface> surface = image->GetFrame( 328 imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_SYNC_DECODE); 329 330 // Ensure that the image's metadata meets our expectations. 331 IntSize imageSize(0, 0); 332 rv = image->GetWidth(&imageSize.width); 333 EXPECT_NS_SUCCEEDED(rv); 334 rv = image->GetHeight(&imageSize.height); 335 EXPECT_NS_SUCCEEDED(rv); 336 337 EXPECT_EQ(testCase.mSize.width, imageSize.width); 338 EXPECT_EQ(testCase.mSize.height, imageSize.height); 339 340 Progress imageProgress = tracker->GetProgress(); 341 342 EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false); 343 EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true); 344 345 // Ensure that we decoded both frames of the image. 346 LookupResult result = 347 SurfaceCache::Lookup(ImageKey(image.get()), 348 RasterSurfaceKey(imageSize, testCase.mSurfaceFlags, 349 PlaybackType::eAnimated), 350 /* aMarkUsed = */ true); 351 ASSERT_EQ(MatchType::EXACT, result.Type()); 352 353 EXPECT_NS_SUCCEEDED(result.Surface().Seek(0)); 354 EXPECT_TRUE(bool(result.Surface())); 355 356 RefPtr<imgFrame> partialFrame = result.Surface().GetFrame(1); 357 EXPECT_TRUE(bool(partialFrame)); 358 }