@amazon-devices/expo-image 2.0.9000000000 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CMakeLists.txt +80 -58
  2. package/README.kepler.md +1 -0
  3. package/build/ExpoImageModules.kepler.d.ts.map +1 -1
  4. package/build/ExpoImageModules.kepler.js +1 -1
  5. package/build/ExpoImageModules.kepler.js.map +1 -1
  6. package/build/ImageViewNativeComponent.js +1 -1
  7. package/build/ImageViewNativeComponent.js.map +1 -1
  8. package/kepler/AutoLinkInit.cpp +17 -0
  9. package/kepler/Constants.h +21 -0
  10. package/kepler/ExpoImage/ExpoImageTurboModule.cpp +148 -0
  11. package/kepler/ExpoImage/ExpoImageTurboModule.h +41 -0
  12. package/kepler/ExpoImage/PrefetchListener.cpp +45 -0
  13. package/kepler/ExpoImage/PrefetchListener.h +40 -0
  14. package/kepler/ExpoImageComponent/AccessibilityDelegate.cpp +33 -0
  15. package/kepler/ExpoImageComponent/AccessibilityDelegate.h +30 -0
  16. package/kepler/ExpoImageComponent/ExpoImageComponentViewManager.cpp +132 -0
  17. package/kepler/ExpoImageComponent/ExpoImageComponentViewManager.h +66 -0
  18. package/kepler/ExpoImageComponent/ExpoImageStore.h +102 -0
  19. package/kepler/ExpoImageComponent/ExpoImageView.cpp +556 -0
  20. package/kepler/ExpoImageComponent/ExpoImageView.h +186 -0
  21. package/kepler/ExpoImageComponent/HashImageDecoder.cpp +91 -0
  22. package/kepler/ExpoImageComponent/HashImageDecoder.h +18 -0
  23. package/kepler/ExpoImageComponent/ImageLoadListeners.cpp +73 -0
  24. package/kepler/ExpoImageComponent/ImageLoadListeners.h +52 -0
  25. package/kepler/ExpoImageComponent/TransitionAnimator.cpp +113 -0
  26. package/kepler/ExpoImageComponent/TransitionAnimator.h +32 -0
  27. package/kepler/ImageSourceInfo.h +21 -0
  28. package/kepler/ViewManagerFactory.cpp +36 -0
  29. package/kepler/ViewManagerFactory.h +29 -0
  30. package/kepler/blurhash/common.h +11 -10
  31. package/kepler/blurhash/decode.cpp +26 -16
  32. package/kepler/contentposition/ImageContentPosition.cpp +50 -69
  33. package/kepler/contentposition/ImageContentPosition.h +36 -26
  34. package/kepler/enums/ImageContentFit.h +47 -15
  35. package/kepler/enums/ImagePriority.h +12 -13
  36. package/kepler/test/blurhash/decode.cpp +36 -27
  37. package/kepler/test/contentposition/ImageContentPosition.cpp +70 -76
  38. package/kepler/test/thumbhash/decode.cpp +18 -10
  39. package/kepler/test/utils/Base64.cpp +73 -0
  40. package/kepler/test/utils/ColorUtils.cpp +53 -0
  41. package/kepler/test/utils/SourceSelection.cpp +87 -0
  42. package/kepler/thumbhash/decode.cpp +7 -16
  43. package/kepler/utils/Base64.cpp +85 -0
  44. package/kepler/utils/Base64.h +36 -0
  45. package/kepler/utils/ColorUtils.h +26 -0
  46. package/kepler/utils/SourceSelection.cpp +80 -0
  47. package/kepler/utils/SourceSelection.h +65 -0
  48. package/lib/aarch64-release/com.amazon.kepler.uitoolkit.react.expoimageapp.so +0 -0
  49. package/lib/aarch64-release/libexpo-image-3.so +0 -0
  50. package/lib/armv7-release/com.amazon.kepler.uitoolkit.react.expoimageapp.so +0 -0
  51. package/lib/armv7-release/libexpo-image-3.so +0 -0
  52. package/lib/x86_64-release/com.amazon.kepler.uitoolkit.react.expoimageapp.so +0 -0
  53. package/lib/x86_64-release/libexpo-image-3.so +0 -0
  54. package/package.json +1 -1
  55. package/react-native.config.js +12 -4
  56. package/src/ExpoImageModules.kepler.ts +1 -1
  57. package/src/ImageViewNativeComponent.ts +1 -1
  58. package/kepler/ExpoImage.cpp +0 -63
  59. package/kepler/ExpoImage.h +0 -29
  60. package/kepler/ExpoImageCacheHandler.cpp +0 -15
  61. package/kepler/ExpoImageCacheHandler.h +0 -28
  62. package/kepler/ExpoImageComponent.cpp +0 -579
  63. package/kepler/ExpoImageComponent.h +0 -199
  64. package/kepler/ImageWrapper/BlurhashImageWrapper.cpp +0 -37
  65. package/kepler/ImageWrapper/BlurhashImageWrapper.h +0 -23
  66. package/kepler/ImageWrapper/CacheImageWrapper.cpp +0 -80
  67. package/kepler/ImageWrapper/CacheImageWrapper.h +0 -41
  68. package/kepler/ImageWrapper/HashImageWrapper.cpp +0 -21
  69. package/kepler/ImageWrapper/HashImageWrapper.h +0 -22
  70. package/kepler/ImageWrapper/ImageWrapper.cpp +0 -24
  71. package/kepler/ImageWrapper/ImageWrapper.h +0 -33
  72. package/kepler/ImageWrapper/LocalImageWrapper.cpp +0 -61
  73. package/kepler/ImageWrapper/LocalImageWrapper.h +0 -30
  74. package/kepler/ImageWrapper/ThumbhashImageWrapper.cpp +0 -33
  75. package/kepler/ImageWrapper/ThumbhashImageWrapper.h +0 -20
  76. package/kepler/KeplerImageViewComponent.h +0 -64
  77. package/kepler/KeplerInit.cpp +0 -35
  78. package/kepler/managers/ExpoImageManager.cpp +0 -38
  79. package/kepler/managers/ExpoImageManager.h +0 -23
  80. package/kepler/test/ExpoImage.cpp +0 -73
  81. package/kepler/test/ExpoImageComponent.cpp +0 -304
  82. package/kepler/test/managers/ExpoImageManager.cpp +0 -39
  83. package/kepler/test/utils/LoaderDelegate.cpp +0 -130
  84. package/kepler/utils/PlaceholderImageLoaderDelegate.cpp +0 -26
  85. package/kepler/utils/PlaceholderImageLoaderDelegate.h +0 -26
  86. package/kepler/utils/PrefetchImageLoaderDelegate.cpp +0 -55
  87. package/kepler/utils/PrefetchImageLoaderDelegate.h +0 -35
  88. package/kepler/utils/SourceImageLoaderDelegate.cpp +0 -47
  89. package/kepler/utils/SourceImageLoaderDelegate.h +0 -42
  90. package/kepler/utils/SourceImageLoaderDelegateEmitter.cpp +0 -15
  91. package/kepler/utils/SourceImageLoaderDelegateEmitter.h +0 -28
  92. package/kepler-compatibility.json +0 -6
@@ -1,100 +1,94 @@
1
1
  #include "../../contentposition/ImageContentPosition.h"
2
2
 
3
- #include <gmock/gmock-actions.h>
4
- #include <gmock/gmock.h>
5
3
  #include <gtest/gtest.h>
6
4
 
7
- #include "catch.hpp"
5
+ namespace expoimage {
6
+ namespace {
8
7
 
9
- TEST_CASE("ImageContentPositionValue") {
10
- SECTION("calcOffset") {
11
- ImageContentPositionValue value{10.0f, false};
8
+ // Helpers to make test intent clearer.
9
+ ImageContentPositionValue px(float v) { return ImageContentPositionValue{v, false}; }
10
+ ImageContentPositionValue empty() { return ImageContentPositionValue{}; }
12
11
 
13
- volta::graphics::Size imageRect{100.0f, 100.0f};
14
- volta::graphics::Size viewRect{50.0f, 50.0f};
12
+ // ---- ImageContentPositionValue ----
15
13
 
16
- ImageContentPositionValue emptyValue;
14
+ TEST(ImageContentPositionValue, EmptyReturnsNullopt) {
15
+ ImageContentPositionValue value;
16
+ Size imageSize{100.0f, 100.0f};
17
+ Size viewSize{50.0f, 50.0f};
17
18
 
18
- auto calc = [](float value,
19
- const volta::graphics::Size& imageRect,
20
- const volta::graphics::Size& viewRect,
21
- bool isPercentage,
22
- bool isReverse) -> float { return 2137.0f; };
19
+ auto calc = [](float, const Size&, const Size&, bool, bool) { return 123.0f; };
20
+ auto result = value.calcOffset(false, imageSize, viewSize, calc);
23
21
 
24
- auto emptyResult = emptyValue.calcOffset(false, imageRect, viewRect, calc);
25
-
26
- EXPECT_FALSE(emptyResult.has_value());
27
-
28
- auto result = value.calcOffset(false, imageRect, viewRect, calc);
29
- EXPECT_EQ(result.value(), 2137.0f);
30
- }
22
+ EXPECT_FALSE(result.has_value());
31
23
  }
32
24
 
33
- TEST_CASE("ImageContentPosition") {
34
- ImageContentPositionValue top{10.0f, false};
35
- ImageContentPositionValue bottom{10.0f, false};
36
- ImageContentPositionValue left{10.0f, false};
37
- ImageContentPositionValue right{10.0f, false};
38
- volta::graphics::Size imageRect{100.0f, 100.0f};
39
- volta::graphics::Size viewRect{50.0f, 50.0f};
40
-
41
- SECTION("offsetX with left position") {
42
- ImageContentPosition pos;
43
-
44
- float offset;
45
-
46
- pos = ImageContentPosition(top, bottom, {}, {10.f, false});
47
- offset = pos.offsetX(imageRect, viewRect);
48
- EXPECT_EQ(offset, 10.f);
49
- }
25
+ TEST(ImageContentPositionValue, SetValueInvokesCalc) {
26
+ ImageContentPositionValue value{10.0f, false};
27
+ Size imageSize{100.0f, 100.0f};
28
+ Size viewSize{50.0f, 50.0f};
50
29
 
51
- SECTION("offsetX with right position") {
52
- ImageContentPosition pos;
30
+ auto calc = [](float, const Size&, const Size&, bool, bool) { return 2137.0f; };
31
+ auto result = value.calcOffset(false, imageSize, viewSize, calc);
53
32
 
54
- float offset;
55
-
56
- pos = ImageContentPosition(top, bottom, {}, {20.f, false});
57
- offset = pos.offsetX(imageRect, viewRect);
58
- EXPECT_EQ(offset, 20.f);
59
- }
60
-
61
- SECTION("offsetX without left and right positions") {
62
- ImageContentPosition pos;
63
-
64
- float offset;
33
+ ASSERT_TRUE(result.has_value());
34
+ EXPECT_FLOAT_EQ(result.value(), 2137.0f);
35
+ }
65
36
 
66
- pos = ImageContentPosition(top, bottom, {}, {});
67
- offset = pos.offsetX(imageRect, viewRect);
68
- EXPECT_EQ(offset, -25.f);
69
- }
37
+ // ---- ImageContentPosition ----
70
38
 
71
- SECTION("offsetY with top position") {
72
- ImageContentPosition pos;
39
+ class ImageContentPositionTest : public ::testing::Test {
40
+ protected:
41
+ Size imageRect_{100.0f, 100.0f};
42
+ Size viewRect_{50.0f, 50.0f};
43
+ };
73
44
 
74
- float offset;
45
+ TEST_F(ImageContentPositionTest, OffsetX_LeftTakesPrecedence) {
46
+ ImageContentPosition pos(empty(), empty(), empty(), px(10.0f));
47
+ EXPECT_FLOAT_EQ(pos.offsetX(imageRect_, viewRect_), 10.0f);
48
+ }
75
49
 
76
- pos = ImageContentPosition({10.f, false}, {}, right, left);
77
- offset = pos.offsetY(imageRect, viewRect);
78
- EXPECT_EQ(offset, 10.f);
79
- }
50
+ TEST_F(ImageContentPositionTest, OffsetX_RightWhenLeftEmpty) {
51
+ // right=20px means image right edge is inset 20px from view right edge.
52
+ // Reverse translation: viewWidth - imageWidth - 20 = 50 - 100 - 20 = -70
53
+ ImageContentPosition pos(empty(), empty(), px(20.0f), empty());
54
+ EXPECT_FLOAT_EQ(pos.offsetX(imageRect_, viewRect_), -70.0f);
55
+ }
80
56
 
81
- SECTION("offsetY with bottom position") {
82
- ImageContentPosition pos;
57
+ TEST_F(ImageContentPositionTest, OffsetX_DefaultCenters) {
58
+ // No left or right → default is center (reverse of 50%): (50-100)/2 = -25
59
+ ImageContentPosition pos(empty(), empty(), empty(), empty());
60
+ EXPECT_FLOAT_EQ(pos.offsetX(imageRect_, viewRect_), -25.0f);
61
+ }
83
62
 
84
- float offset;
63
+ TEST_F(ImageContentPositionTest, OffsetY_TopTakesPrecedence) {
64
+ ImageContentPosition pos(px(10.0f), empty(), empty(), empty());
65
+ EXPECT_FLOAT_EQ(pos.offsetY(imageRect_, viewRect_), 10.0f);
66
+ }
85
67
 
86
- pos = ImageContentPosition({}, {10.f, false}, right, left);
87
- offset = pos.offsetY(imageRect, viewRect);
88
- EXPECT_EQ(offset, -60.f);
89
- }
68
+ TEST_F(ImageContentPositionTest, OffsetY_BottomWhenTopEmpty) {
69
+ // bottom=10 → reverse: viewH - imageH - 10 = 50 - 100 - 10 = -60
70
+ ImageContentPosition pos(empty(), px(10.0f), empty(), empty());
71
+ EXPECT_FLOAT_EQ(pos.offsetY(imageRect_, viewRect_), -60.0f);
72
+ }
90
73
 
91
- SECTION("offsetY without top or bottom positions") {
92
- ImageContentPosition pos;
74
+ TEST_F(ImageContentPositionTest, OffsetY_DefaultCenters) {
75
+ ImageContentPosition pos(empty(), empty(), empty(), empty());
76
+ EXPECT_FLOAT_EQ(pos.offsetY(imageRect_, viewRect_), -25.0f);
77
+ }
93
78
 
94
- float offset;
79
+ TEST_F(ImageContentPositionTest, Percentage_LeftAtZero) {
80
+ // left=0% → 0
81
+ ImageContentPositionValue leftPct{0.0f, true};
82
+ ImageContentPosition pos(empty(), empty(), empty(), leftPct);
83
+ EXPECT_FLOAT_EQ(pos.offsetX(imageRect_, viewRect_), 0.0f);
84
+ }
95
85
 
96
- pos = ImageContentPosition({}, {}, right, left);
97
- offset = pos.offsetY(imageRect, viewRect);
98
- EXPECT_EQ(offset, -25.f);
99
- }
86
+ TEST_F(ImageContentPositionTest, Percentage_LeftAt100) {
87
+ // left=100% → viewW - imageW = 50 - 100 = -50
88
+ ImageContentPositionValue leftPct{100.0f, true};
89
+ ImageContentPosition pos(empty(), empty(), empty(), leftPct);
90
+ EXPECT_FLOAT_EQ(pos.offsetX(imageRect_, viewRect_), -50.0f);
100
91
  }
92
+
93
+ } // namespace
94
+ } // namespace expoimage
@@ -1,18 +1,26 @@
1
+ /*
2
+ * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ *
4
+ * PROPRIETARY/CONFIDENTIAL
5
+ * Use is subject to license terms.
6
+ */
7
+
1
8
  #include "../../thumbhash/decode.h"
2
9
 
3
- #include <gmock/gmock-actions.h>
4
- #include <gmock/gmock.h>
5
10
  #include <gtest/gtest.h>
6
11
 
7
- #include "catch.hpp"
12
+ namespace expoimage {
13
+ namespace {
8
14
 
9
- TEST_CASE("thumbhash") {
10
- std::string hash = "1QcSHQRnh493V4dIh4eXh1h4kJUI";
15
+ TEST(ThumbhashDecode, DecodesKnownDimensions) {
16
+ const std::string hash = "1QcSHQRnh493V4dIh4eXh1h4kJUI";
11
17
 
12
- SECTION("decodeThumbhash") {
13
- auto [width, height, pixelArray] = decodeThumbhash(hash);
18
+ auto result = decodeThumbhash(hash);
14
19
 
15
- EXPECT_EQ(width, 23);
16
- EXPECT_EQ(height, 32);
17
- }
20
+ EXPECT_EQ(result.width, 23);
21
+ EXPECT_EQ(result.height, 32);
22
+ ASSERT_NE(result.pixelArray, nullptr);
18
23
  }
24
+
25
+ } // namespace
26
+ } // namespace expoimage
@@ -0,0 +1,73 @@
1
+ #include "../../utils/Base64.h"
2
+
3
+ #include <gtest/gtest.h>
4
+
5
+ #include <string>
6
+
7
+ namespace expoimage {
8
+ namespace {
9
+
10
+ std::string decode(const std::string& in) {
11
+ std::string out;
12
+ EXPECT_TRUE(decodeBase64(in, out));
13
+ return out;
14
+ }
15
+
16
+ TEST(DecodeBase64, DecodesSvg) {
17
+ EXPECT_EQ(decode("PHN2Zy8+"), "<svg/>");
18
+ }
19
+
20
+ TEST(DecodeBase64, EmptyInput) {
21
+ std::string out;
22
+ EXPECT_TRUE(decodeBase64("", out));
23
+ EXPECT_EQ(out, "");
24
+ }
25
+
26
+ TEST(DecodeBase64, NoPadding) {
27
+ // "Man" -> "TWFu" (exact multiple of 4, no padding)
28
+ EXPECT_EQ(decode("TWFu"), "Man");
29
+ }
30
+
31
+ TEST(DecodeBase64, OnePadByte) {
32
+ // "Ma" -> "TWE=" (one '=' -> two output bytes)
33
+ EXPECT_EQ(decode("TWE="), "Ma");
34
+ }
35
+
36
+ TEST(DecodeBase64, TwoPadBytes) {
37
+ // "M" -> "TQ==" (two '=' -> one output byte)
38
+ EXPECT_EQ(decode("TQ=="), "M");
39
+ }
40
+
41
+ TEST(DecodeBase64, UnpaddedRemainder) {
42
+ // Same payloads as the padded cases but without '=' padding.
43
+ EXPECT_EQ(decode("TWE"), "Ma");
44
+ EXPECT_EQ(decode("TQ"), "M");
45
+ }
46
+
47
+ TEST(DecodeBase64, PlusAndSlash) {
48
+ // bytes {0xFB, 0xFF, 0xBF} -> "+/+/"
49
+ EXPECT_EQ(decode("+/+/"), std::string("\xFB\xFF\xBF", 3));
50
+ }
51
+
52
+ TEST(DecodeBase64, RejectsInvalidCharacter) {
53
+ std::string out;
54
+ EXPECT_FALSE(decodeBase64("TW*u", out));
55
+ }
56
+
57
+ TEST(DecodeBase64, RejectsWhitespace) {
58
+ std::string out;
59
+ EXPECT_FALSE(decodeBase64("TW Fu", out));
60
+ }
61
+
62
+ TEST(DecodeBase64, RejectsDataAfterPadding) {
63
+ std::string out;
64
+ EXPECT_FALSE(decodeBase64("TQ==A", out));
65
+ }
66
+
67
+ TEST(DecodeBase64, RejectsLoneRemainderByte) {
68
+ std::string out;
69
+ EXPECT_FALSE(decodeBase64("TWFuA", out));
70
+ }
71
+
72
+ } // namespace
73
+ } // namespace expoimage
@@ -0,0 +1,53 @@
1
+ #include "../../utils/ColorUtils.h"
2
+
3
+ #include <gtest/gtest.h>
4
+
5
+ namespace expoimage {
6
+ namespace {
7
+
8
+ using Color = apmf::iface::com::amazon::kepler::uitoolkit::graphics::Color;
9
+
10
+ constexpr float kTolerance = 1.0f / 255.0f;
11
+
12
+ TEST(IntToColor, FullyOpaqueBlack) {
13
+ Color c = intToColor(0xFF000000);
14
+ EXPECT_NEAR(c.a, 1.0f, kTolerance);
15
+ EXPECT_NEAR(c.r, 0.0f, kTolerance);
16
+ EXPECT_NEAR(c.g, 0.0f, kTolerance);
17
+ EXPECT_NEAR(c.b, 0.0f, kTolerance);
18
+ }
19
+
20
+ TEST(IntToColor, FullyOpaqueWhite) {
21
+ Color c = intToColor(0xFFFFFFFF);
22
+ EXPECT_NEAR(c.a, 1.0f, kTolerance);
23
+ EXPECT_NEAR(c.r, 1.0f, kTolerance);
24
+ EXPECT_NEAR(c.g, 1.0f, kTolerance);
25
+ EXPECT_NEAR(c.b, 1.0f, kTolerance);
26
+ }
27
+
28
+ TEST(IntToColor, TransparentBlack) {
29
+ Color c = intToColor(0x00000000);
30
+ EXPECT_NEAR(c.a, 0.0f, kTolerance);
31
+ EXPECT_NEAR(c.r, 0.0f, kTolerance);
32
+ EXPECT_NEAR(c.g, 0.0f, kTolerance);
33
+ EXPECT_NEAR(c.b, 0.0f, kTolerance);
34
+ }
35
+
36
+ TEST(IntToColor, ARGBChannelOrdering) {
37
+ Color c = intToColor(0x12345678);
38
+ EXPECT_NEAR(c.a, 0x12 / 255.0f, kTolerance);
39
+ EXPECT_NEAR(c.r, 0x34 / 255.0f, kTolerance);
40
+ EXPECT_NEAR(c.g, 0x56 / 255.0f, kTolerance);
41
+ EXPECT_NEAR(c.b, 0x78 / 255.0f, kTolerance);
42
+ }
43
+
44
+ TEST(IntToColor, SignBitSetIsWellDefined) {
45
+ Color c = intToColor(static_cast<int>(0xFF112233u));
46
+ EXPECT_NEAR(c.a, 1.0f, kTolerance);
47
+ EXPECT_NEAR(c.r, 0x11 / 255.0f, kTolerance);
48
+ EXPECT_NEAR(c.g, 0x22 / 255.0f, kTolerance);
49
+ EXPECT_NEAR(c.b, 0x33 / 255.0f, kTolerance);
50
+ }
51
+
52
+ } // namespace
53
+ } // namespace expoimage
@@ -0,0 +1,87 @@
1
+ #include "../../utils/SourceSelection.h"
2
+
3
+ #include <gtest/gtest.h>
4
+
5
+ namespace expoimage {
6
+ namespace {
7
+
8
+ using Size = apmf::iface::com::amazon::kepler::uitoolkit::graphics::Size;
9
+
10
+ // ---- startsWith (ASCII case-insensitive) ----
11
+
12
+ TEST(StartsWith, ExactPrefix) {
13
+ EXPECT_TRUE(startsWith("blurhash:abc", "blurhash"));
14
+ EXPECT_TRUE(startsWith("thumbhash:abc", "thumbhash"));
15
+ }
16
+
17
+ TEST(StartsWith, CaseInsensitive) {
18
+ EXPECT_TRUE(startsWith("BlurHash:abc", "blurhash"));
19
+ EXPECT_TRUE(startsWith("BLURHASH", "blurhash"));
20
+ EXPECT_TRUE(startsWith("blurhash", "BLURHASH"));
21
+ EXPECT_TRUE(startsWith("ThumbHash:abc", "thumbhash"));
22
+ }
23
+
24
+ TEST(StartsWith, NoMatch) {
25
+ EXPECT_FALSE(startsWith("https://example.com", "blurhash"));
26
+ EXPECT_FALSE(startsWith("", "blurhash"));
27
+ EXPECT_FALSE(startsWith("blur", "blurhash")); // str shorter than prefix
28
+ }
29
+
30
+ TEST(StartsWith, EmptyPrefix) {
31
+ EXPECT_TRUE(startsWith("anything", ""));
32
+ EXPECT_TRUE(startsWith("", ""));
33
+ }
34
+
35
+ // ---- uriDecode ----
36
+
37
+ TEST(UriDecode, PassthroughPlainAscii) {
38
+ EXPECT_EQ(uriDecode("hello"), "hello");
39
+ EXPECT_EQ(uriDecode(""), "");
40
+ }
41
+
42
+ TEST(UriDecode, DecodesHexEscapes) {
43
+ EXPECT_EQ(uriDecode("a%20b"), "a b");
44
+ EXPECT_EQ(uriDecode("%5E"), "^");
45
+ EXPECT_EQ(uriDecode("%2F"), "/");
46
+ EXPECT_EQ(uriDecode("%2f"), "/");
47
+ }
48
+
49
+ TEST(UriDecode, MixedContent) {
50
+ EXPECT_EQ(uriDecode("https://example.com/path%20with%20spaces?q=%3F"),
51
+ "https://example.com/path with spaces?q=?");
52
+ }
53
+
54
+ TEST(UriDecode, MalformedEscapeIsLeftAsIs) {
55
+ EXPECT_EQ(uriDecode("50%"), "50%");
56
+ EXPECT_EQ(uriDecode("%ZZ"), "%ZZ");
57
+ EXPECT_EQ(uriDecode("%2"), "%2");
58
+ }
59
+
60
+ // ---- getBestSource ----
61
+
62
+ TEST(GetBestSource, EmptyReturnsNullopt) {
63
+ std::vector<ImageSourceInfo> sources;
64
+ auto result = getBestSource(sources, Size{100.0f, 100.0f});
65
+ EXPECT_FALSE(result.has_value());
66
+ }
67
+
68
+ TEST(GetBestSource, SingleSourceIsAlwaysReturned) {
69
+ std::vector<ImageSourceInfo> sources{{"uri", 50.0f, 50.0f}};
70
+ auto result = getBestSource(sources, Size{100.0f, 100.0f});
71
+ ASSERT_TRUE(result.has_value());
72
+ EXPECT_EQ(result->uri, "uri");
73
+ }
74
+
75
+ TEST(GetBestSource, PicksClosestPixelCountMatch) {
76
+ std::vector<ImageSourceInfo> sources{
77
+ {"small", 10.0f, 10.0f},
78
+ {"medium", 100.0f, 100.0f},
79
+ {"large", 500.0f, 500.0f}};
80
+
81
+ auto result = getBestSource(sources, Size{100.0f, 100.0f});
82
+ ASSERT_TRUE(result.has_value());
83
+ EXPECT_EQ(result->uri, "medium");
84
+ }
85
+
86
+ } // namespace
87
+ } // namespace expoimage
@@ -7,12 +7,10 @@
7
7
  */
8
8
 
9
9
  #include "decode.h"
10
-
11
- #include <mbedtls/base64.h>
10
+ #include "../utils/Base64.h"
12
11
 
13
12
  #include <cmath>
14
13
  #include <string>
15
- #include <unordered_map>
16
14
  #include <vector>
17
15
 
18
16
  // Inspired by https://github.com/evanw/thumbhash/blob/main/java/com/madebyevan/thumbhash/ThumbHash.java
@@ -20,19 +18,12 @@
20
18
  namespace {
21
19
 
22
20
  std::string base64_decode(const std::string& in) {
23
- size_t bufferSize;
24
-
25
- mbedtls_base64_decode(nullptr, 0, &bufferSize, reinterpret_cast<const unsigned char*>(in.data()), in.size());
26
-
27
- std::string buffer(bufferSize, ' ');
28
-
29
- mbedtls_base64_decode(
30
- reinterpret_cast<unsigned char*>(buffer.data()),
31
- bufferSize,
32
- &bufferSize,
33
- reinterpret_cast<const unsigned char*>(in.data()),
34
- in.size());
35
-
21
+ std::string buffer;
22
+ if (!expoimage::decodeBase64(in, buffer)) {
23
+ // Return empty on malformed input so the size check in the caller
24
+ // rejects the hash instead of decoding a partial buffer into garbage pixels
25
+ return "";
26
+ }
36
27
  return buffer;
37
28
  }
38
29
 
@@ -0,0 +1,85 @@
1
+ /*
2
+ * base64 decoding.
3
+ *
4
+ * Adapted from base64.cpp by René Nyffenegger (zlib license). Altered from the
5
+ * original: decode-only, strict RFC 4648 validation, and a bool success return.
6
+ *
7
+ * Copyright (C) 2004-2017 René Nyffenegger
8
+ *
9
+ * This source code is provided 'as-is', without any express or implied
10
+ * warranty. In no event will the author be held liable for any damages
11
+ * arising from the use of this software.
12
+ *
13
+ * Permission is granted to anyone to use this software for any purpose,
14
+ * including commercial applications, and to alter it and redistribute it
15
+ * freely, subject to the following restrictions:
16
+ *
17
+ * 1. The origin of this source code must not be misrepresented; you must not
18
+ * claim that you wrote the original source code. If you use this source code
19
+ * in a product, an acknowledgment in the product documentation would be
20
+ * appreciated but is not required.
21
+ * 2. Altered source versions must be plainly marked as such, and must not be
22
+ * misrepresented as being the original source code.
23
+ * 3. This notice may not be removed or altered from any source distribution.
24
+ *
25
+ * René Nyffenegger rene.nyffenegger@adp-gmbh.ch
26
+ */
27
+
28
+ #include "Base64.h"
29
+
30
+ namespace expoimage {
31
+
32
+ namespace {
33
+
34
+ int decodeValue(unsigned char c) {
35
+ if (c >= 'A' && c <= 'Z') return c - 'A';
36
+ if (c >= 'a' && c <= 'z') return c - 'a' + 26;
37
+ if (c >= '0' && c <= '9') return c - '0' + 52;
38
+ if (c == '+') return 62;
39
+ if (c == '/') return 63;
40
+ return -1;
41
+ }
42
+
43
+ } // namespace
44
+
45
+ bool decodeBase64(const std::string& input, std::string& output) {
46
+ output.clear();
47
+ output.reserve(input.size() / 4 * 3);
48
+
49
+ unsigned char block[4];
50
+ size_t filled = 0;
51
+ size_t padding = 0;
52
+
53
+ for (unsigned char c : input) {
54
+ if (c == '=') {
55
+ ++padding;
56
+ continue;
57
+ }
58
+ if (padding != 0) return false;
59
+
60
+ const int value = decodeValue(c);
61
+ if (value < 0) return false;
62
+
63
+ block[filled++] = static_cast<unsigned char>(value);
64
+ if (filled == 4) {
65
+ output.push_back(static_cast<char>((block[0] << 2) + ((block[1] & 0x30) >> 4)));
66
+ output.push_back(static_cast<char>(((block[1] & 0x0f) << 4) + ((block[2] & 0x3c) >> 2)));
67
+ output.push_back(static_cast<char>(((block[2] & 0x03) << 6) + block[3]));
68
+ filled = 0;
69
+ }
70
+ }
71
+
72
+ if (padding > 2) return false;
73
+ if (filled == 1) return false;
74
+
75
+ if (filled >= 2) {
76
+ output.push_back(static_cast<char>((block[0] << 2) + ((block[1] & 0x30) >> 4)));
77
+ if (filled == 3) {
78
+ output.push_back(static_cast<char>(((block[1] & 0x0f) << 4) + ((block[2] & 0x3c) >> 2)));
79
+ }
80
+ }
81
+
82
+ return true;
83
+ }
84
+
85
+ } // namespace expoimage
@@ -0,0 +1,36 @@
1
+ /*
2
+ * base64 decoding.
3
+ *
4
+ * Adapted from base64.cpp by René Nyffenegger (zlib license). Altered from the
5
+ * original: decode-only, strict RFC 4648 validation, and a bool success return.
6
+ *
7
+ * Copyright (C) 2004-2017 René Nyffenegger
8
+ *
9
+ * This source code is provided 'as-is', without any express or implied
10
+ * warranty. In no event will the author be held liable for any damages
11
+ * arising from the use of this software.
12
+ *
13
+ * Permission is granted to anyone to use this software for any purpose,
14
+ * including commercial applications, and to alter it and redistribute it
15
+ * freely, subject to the following restrictions:
16
+ *
17
+ * 1. The origin of this source code must not be misrepresented; you must not
18
+ * claim that you wrote the original source code. If you use this source code
19
+ * in a product, an acknowledgment in the product documentation would be
20
+ * appreciated but is not required.
21
+ * 2. Altered source versions must be plainly marked as such, and must not be
22
+ * misrepresented as being the original source code.
23
+ * 3. This notice may not be removed or altered from any source distribution.
24
+ *
25
+ * René Nyffenegger rene.nyffenegger@adp-gmbh.ch
26
+ */
27
+
28
+ #pragma once
29
+
30
+ #include <string>
31
+
32
+ namespace expoimage {
33
+
34
+ [[nodiscard]] bool decodeBase64(const std::string& input, std::string& output);
35
+
36
+ } // namespace expoimage
@@ -0,0 +1,26 @@
1
+ /*
2
+ * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ *
4
+ * PROPRIETARY/CONFIDENTIAL
5
+ * Use is subject to license terms.
6
+ */
7
+
8
+ #pragma once
9
+
10
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/graphics/Color.h>
11
+
12
+ #include <cstdint>
13
+
14
+ namespace expoimage {
15
+
16
+ /** Converts an ARGB integer (from processColor in JS) to a stable Color struct. */
17
+ inline apmf::iface::com::amazon::kepler::uitoolkit::graphics::Color intToColor(int argb) {
18
+ auto u = static_cast<uint32_t>(argb);
19
+ float a = ((u >> 24) & 0xFF) / 255.0f;
20
+ float r = ((u >> 16) & 0xFF) / 255.0f;
21
+ float g = ((u >> 8) & 0xFF) / 255.0f;
22
+ float b = (u & 0xFF) / 255.0f;
23
+ return {r, g, b, a};
24
+ }
25
+
26
+ } // namespace expoimage