@dynlabs/react-native-image-to-webp 0.1.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 (38) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +247 -0
  3. package/ReactNativeImageToWebp.podspec +35 -0
  4. package/android/build.gradle +85 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/cpp/CMakeLists.txt +67 -0
  7. package/android/src/main/cpp/ImageToWebPJNI.cpp +73 -0
  8. package/android/src/main/java/com/dynlabs/reactnativeimagetowebp/ReactNativeImageToWebpModule.kt +258 -0
  9. package/android/src/main/java/com/dynlabs/reactnativeimagetowebp/ReactNativeImageToWebpPackage.kt +33 -0
  10. package/cpp/ImageToWebP.cpp +132 -0
  11. package/cpp/ImageToWebP.h +41 -0
  12. package/cpp/README.md +21 -0
  13. package/cpp/SETUP.md +71 -0
  14. package/ios/ReactNativeImageToWebp.h +5 -0
  15. package/ios/ReactNativeImageToWebp.mm +342 -0
  16. package/lib/module/NativeReactNativeImageToWebp.js +5 -0
  17. package/lib/module/NativeReactNativeImageToWebp.js.map +1 -0
  18. package/lib/module/index.js +78 -0
  19. package/lib/module/index.js.map +1 -0
  20. package/lib/module/package.json +1 -0
  21. package/lib/module/presets.js +64 -0
  22. package/lib/module/presets.js.map +1 -0
  23. package/lib/module/validation.js +36 -0
  24. package/lib/module/validation.js.map +1 -0
  25. package/lib/typescript/package.json +1 -0
  26. package/lib/typescript/src/NativeReactNativeImageToWebp.d.ts +24 -0
  27. package/lib/typescript/src/NativeReactNativeImageToWebp.d.ts.map +1 -0
  28. package/lib/typescript/src/index.d.ts +35 -0
  29. package/lib/typescript/src/index.d.ts.map +1 -0
  30. package/lib/typescript/src/presets.d.ts +3 -0
  31. package/lib/typescript/src/presets.d.ts.map +1 -0
  32. package/lib/typescript/src/validation.d.ts +9 -0
  33. package/lib/typescript/src/validation.d.ts.map +1 -0
  34. package/package.json +139 -0
  35. package/src/NativeReactNativeImageToWebp.ts +32 -0
  36. package/src/index.tsx +109 -0
  37. package/src/presets.ts +80 -0
  38. package/src/validation.ts +55 -0
@@ -0,0 +1,258 @@
1
+ package com.dynlabs.reactnativeimagetowebp
2
+
3
+ import android.graphics.Bitmap
4
+ import android.graphics.BitmapFactory
5
+ import android.graphics.ImageDecoder
6
+ import android.net.Uri
7
+ import android.os.Build
8
+ import com.facebook.react.bridge.Arguments
9
+ import com.facebook.react.bridge.Promise
10
+ import com.facebook.react.bridge.ReactApplicationContext
11
+ import com.facebook.react.bridge.ReadableMap
12
+ import com.facebook.react.bridge.WritableMap
13
+ import java.io.File
14
+ import java.io.FileOutputStream
15
+ import java.util.concurrent.ExecutorService
16
+ import java.util.concurrent.Executors
17
+
18
+ class ReactNativeImageToWebpModule(reactContext: ReactApplicationContext) :
19
+ NativeReactNativeImageToWebpSpec(reactContext) {
20
+
21
+ private val executor: ExecutorService = Executors.newSingleThreadExecutor()
22
+
23
+ companion object {
24
+ init {
25
+ System.loadLibrary("react-native-image-to-webp")
26
+ }
27
+
28
+ const val NAME = NativeReactNativeImageToWebpSpec.NAME
29
+
30
+ private const val ERROR_CODE_INVALID_INPUT = "INVALID_INPUT"
31
+ private const val ERROR_CODE_FILE_NOT_FOUND = "FILE_NOT_FOUND"
32
+ private const val ERROR_CODE_DECODE_FAILED = "DECODE_FAILED"
33
+ private const val ERROR_CODE_ENCODE_FAILED = "ENCODE_FAILED"
34
+ private const val ERROR_CODE_IO_ERROR = "IO_ERROR"
35
+ private const val ERROR_CODE_UNSUPPORTED_FORMAT = "UNSUPPORTED_FORMAT"
36
+ }
37
+
38
+ // Native JNI methods
39
+ private external fun nativeEncodeWebP(
40
+ rgbaData: ByteArray,
41
+ width: Int,
42
+ height: Int,
43
+ quality: Int,
44
+ method: Int,
45
+ lossless: Boolean,
46
+ outputPath: String
47
+ ): Boolean
48
+
49
+ private external fun nativeGetLastError(): String
50
+
51
+ override fun convertImageToWebP(
52
+ options: ReadableMap,
53
+ promise: Promise
54
+ ) {
55
+ executor.execute {
56
+ try {
57
+ val result = convertImageToWebPInternal(options)
58
+ promise.resolve(result)
59
+ } catch (e: Exception) {
60
+ promise.reject(
61
+ when (e) {
62
+ is FileNotFoundException -> ERROR_CODE_FILE_NOT_FOUND
63
+ is IllegalArgumentException -> ERROR_CODE_INVALID_INPUT
64
+ is UnsupportedOperationException -> ERROR_CODE_UNSUPPORTED_FORMAT
65
+ else -> ERROR_CODE_DECODE_FAILED
66
+ },
67
+ e.message ?: "Unknown error",
68
+ e
69
+ )
70
+ }
71
+ }
72
+ }
73
+
74
+ private fun convertImageToWebPInternal(options: ReadableMap): WritableMap {
75
+ // Parse options
76
+ val inputPath = options.getString("inputPath")
77
+ ?: throw IllegalArgumentException("inputPath is required")
78
+
79
+ val outputPath = options.getString("outputPath")
80
+ ?: deriveOutputPath(inputPath)
81
+
82
+ val maxLongEdge = if (options.hasKey("maxLongEdge")) {
83
+ options.getDouble("maxLongEdge").toInt()
84
+ } else {
85
+ null
86
+ }
87
+
88
+ val quality = if (options.hasKey("quality")) {
89
+ options.getInt("quality")
90
+ } else {
91
+ 80
92
+ }
93
+
94
+ val method = if (options.hasKey("method")) {
95
+ options.getInt("method")
96
+ } else {
97
+ 3
98
+ }
99
+
100
+ val lossless = options.hasKey("lossless") && options.getBoolean("lossless")
101
+
102
+ // Validate
103
+ if (maxLongEdge != null && maxLongEdge <= 0) {
104
+ throw IllegalArgumentException("maxLongEdge must be positive")
105
+ }
106
+ if (quality < 0 || quality > 100) {
107
+ throw IllegalArgumentException("quality must be between 0 and 100")
108
+ }
109
+ if (method < 0 || method > 6) {
110
+ throw IllegalArgumentException("method must be between 0 and 6")
111
+ }
112
+
113
+ // Check input file
114
+ val inputFile = File(inputPath)
115
+ if (!inputFile.exists() || !inputFile.canRead()) {
116
+ throw FileNotFoundException("File not found: $inputPath")
117
+ }
118
+
119
+ // Decode image
120
+ val bitmap = decodeImage(inputFile, maxLongEdge)
121
+ ?: throw RuntimeException("Failed to decode image")
122
+
123
+ val width = bitmap.width
124
+ val height = bitmap.height
125
+
126
+ // Convert bitmap to RGBA
127
+ val rgbaData = bitmapToRGBA(bitmap)
128
+ bitmap.recycle()
129
+
130
+ // Ensure output directory exists
131
+ val outputFile = File(outputPath)
132
+ outputFile.parentFile?.mkdirs()
133
+
134
+ // Encode to WebP using native code
135
+ val success = nativeEncodeWebP(
136
+ rgbaData,
137
+ width,
138
+ height,
139
+ quality,
140
+ method,
141
+ lossless,
142
+ outputPath
143
+ )
144
+
145
+ if (!success) {
146
+ val errorMsg = nativeGetLastError()
147
+ throw RuntimeException("WebP encoding failed: $errorMsg")
148
+ }
149
+
150
+ // Get file size
151
+ val sizeBytes = outputFile.length()
152
+
153
+ // Return result
154
+ val result = Arguments.createMap()
155
+ result.putString("outputPath", outputPath)
156
+ result.putInt("width", width)
157
+ result.putInt("height", height)
158
+ result.putDouble("sizeBytes", sizeBytes.toDouble())
159
+
160
+ return result
161
+ }
162
+
163
+ private fun decodeImage(file: File, maxLongEdge: Int?): Bitmap? {
164
+ return try {
165
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
166
+ // Use ImageDecoder for modern Android
167
+ val source = ImageDecoder.createSource(file)
168
+ val decoder = ImageDecoder.decodeBitmap(source) { decoder, info, source ->
169
+ // Apply sampling if maxLongEdge is specified
170
+ maxLongEdge?.let { maxEdge ->
171
+ val originalWidth = info.size.width
172
+ val originalHeight = info.size.height
173
+ val maxDimension = maxOf(originalWidth, originalHeight)
174
+ if (maxDimension > maxEdge) {
175
+ val sampleSize = (maxDimension / maxEdge).toInt().coerceAtLeast(1)
176
+ decoder.setTargetSampleSize(sampleSize)
177
+ }
178
+ }
179
+ }
180
+ decoder
181
+ } else {
182
+ // Fallback to BitmapFactory
183
+ val options = BitmapFactory.Options().apply {
184
+ inJustDecodeBounds = true
185
+ }
186
+ BitmapFactory.decodeFile(file.absolutePath, options)
187
+
188
+ maxLongEdge?.let { maxEdge ->
189
+ val maxDimension = maxOf(options.outWidth, options.outHeight)
190
+ if (maxDimension > maxEdge) {
191
+ val sampleSize = (maxDimension / maxEdge).toInt().coerceAtLeast(1)
192
+ options.inSampleSize = sampleSize
193
+ }
194
+ }
195
+
196
+ options.inJustDecodeBounds = false
197
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888
198
+
199
+ var bitmap = BitmapFactory.decodeFile(file.absolutePath, options)
200
+ ?: return null
201
+
202
+ // Apply EXIF orientation if needed
203
+ bitmap = applyExifOrientation(bitmap, file)
204
+
205
+ // Final resize if still needed (inSampleSize is approximate)
206
+ maxLongEdge?.let { maxEdge ->
207
+ val currentMax = maxOf(bitmap.width, bitmap.height)
208
+ if (currentMax > maxEdge) {
209
+ val scale = maxEdge.toFloat() / currentMax
210
+ val newWidth = (bitmap.width * scale).toInt()
211
+ val newHeight = (bitmap.height * scale).toInt()
212
+ val resized = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
213
+ if (resized != bitmap) {
214
+ bitmap.recycle()
215
+ bitmap = resized
216
+ }
217
+ }
218
+ }
219
+
220
+ bitmap
221
+ }
222
+ } catch (e: Exception) {
223
+ throw RuntimeException("Failed to decode image: ${e.message}", e)
224
+ }
225
+ }
226
+
227
+ private fun applyExifOrientation(bitmap: Bitmap, file: File): Bitmap {
228
+ // Note: For full EXIF support, use ExifInterface
229
+ // For now, return bitmap as-is
230
+ // TODO: Implement EXIF orientation handling using android.media.ExifInterface
231
+ return bitmap
232
+ }
233
+
234
+ private fun bitmapToRGBA(bitmap: Bitmap): ByteArray {
235
+ val width = bitmap.width
236
+ val height = bitmap.height
237
+ val pixels = IntArray(width * height)
238
+ bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
239
+
240
+ val rgbaData = ByteArray(width * height * 4)
241
+ var index = 0
242
+ for (pixel in pixels) {
243
+ rgbaData[index++] = ((pixel shr 16) and 0xFF).toByte() // R
244
+ rgbaData[index++] = ((pixel shr 8) and 0xFF).toByte() // G
245
+ rgbaData[index++] = (pixel and 0xFF).toByte() // B
246
+ rgbaData[index++] = ((pixel shr 24) and 0xFF).toByte() // A
247
+ }
248
+
249
+ return rgbaData
250
+ }
251
+
252
+ private fun deriveOutputPath(inputPath: String): String {
253
+ val inputFile = File(inputPath)
254
+ val directory = inputFile.parent
255
+ val filename = inputFile.nameWithoutExtension
256
+ return File(directory, "$filename.webp").absolutePath
257
+ }
258
+ }
@@ -0,0 +1,33 @@
1
+ package com.dynlabs.reactnativeimagetowebp
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+ import java.util.HashMap
9
+
10
+ class ReactNativeImageToWebpPackage : BaseReactPackage() {
11
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12
+ return if (name == ReactNativeImageToWebpModule.NAME) {
13
+ ReactNativeImageToWebpModule(reactContext)
14
+ } else {
15
+ null
16
+ }
17
+ }
18
+
19
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
20
+ return ReactModuleInfoProvider {
21
+ val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
22
+ moduleInfos[ReactNativeImageToWebpModule.NAME] = ReactModuleInfo(
23
+ ReactNativeImageToWebpModule.NAME,
24
+ ReactNativeImageToWebpModule.NAME,
25
+ false, // canOverrideExistingModule
26
+ false, // needsEagerInit
27
+ false, // isCxxModule
28
+ true // isTurboModule
29
+ )
30
+ moduleInfos
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,132 @@
1
+ #include "ImageToWebP.h"
2
+ #include <fstream>
3
+ #include <vector>
4
+ #include <cstring>
5
+
6
+ // libwebp includes (uncomment after vendoring libwebp)
7
+ #ifdef WEBP_AVAILABLE
8
+ #include "webp/encode.h"
9
+ #include "webp/mux.h"
10
+ #endif
11
+
12
+ WebPEncodeResult encodeWebP(
13
+ const uint8_t* rgbaData,
14
+ uint32_t width,
15
+ uint32_t height,
16
+ const WebPEncodeOptions& options,
17
+ const std::string& outputPath) {
18
+ WebPEncodeResult result;
19
+ result.width = width;
20
+ result.height = height;
21
+
22
+ #ifdef WEBP_AVAILABLE
23
+ // Initialize WebP config
24
+ WebPConfig config;
25
+ if (!WebPConfigInit(&config)) {
26
+ result.success = false;
27
+ result.errorMessage = "Failed to initialize WebP config";
28
+ return result;
29
+ }
30
+
31
+ // Apply preset
32
+ WebPPreset preset = WEBP_PRESET_DEFAULT;
33
+ if (options.lossless) {
34
+ preset = WEBP_PRESET_DEFAULT; // Will be overridden by lossless flag
35
+ }
36
+
37
+ if (!WebPConfigPreset(&config, preset, options.quality)) {
38
+ result.success = false;
39
+ result.errorMessage = "Failed to configure WebP preset";
40
+ return result;
41
+ }
42
+
43
+ // Override with options
44
+ config.quality = static_cast<float>(options.quality);
45
+ config.method = options.method;
46
+ config.lossless = options.lossless ? 1 : 0;
47
+ config.exact = options.exact ? 1 : 0;
48
+
49
+ // Thread level (if supported)
50
+ #ifdef WEBP_USE_THREAD
51
+ config.thread_level = options.threadLevel;
52
+ #endif
53
+
54
+ // Validate config
55
+ if (!WebPValidateConfig(&config)) {
56
+ result.success = false;
57
+ result.errorMessage = "Invalid WebP config";
58
+ return result;
59
+ }
60
+
61
+ // Initialize picture
62
+ WebPPicture picture;
63
+ if (!WebPPictureInit(&picture)) {
64
+ result.success = false;
65
+ result.errorMessage = "Failed to initialize WebP picture";
66
+ return result;
67
+ }
68
+
69
+ picture.width = static_cast<int>(width);
70
+ picture.height = static_cast<int>(height);
71
+ picture.use_argb = options.lossless ? 1 : 0; // Use ARGB for lossless
72
+
73
+ // Import RGBA data
74
+ if (!WebPPictureImportRGBA(&picture, rgbaData, static_cast<int>(width * 4))) {
75
+ WebPPictureFree(&picture);
76
+ result.success = false;
77
+ result.errorMessage = "Failed to import RGBA data";
78
+ return result;
79
+ }
80
+
81
+ // Setup memory writer for output
82
+ WebPMemoryWriter writer;
83
+ WebPMemoryWriterInit(&writer);
84
+ picture.writer = WebPMemoryWriterWrite;
85
+ picture.custom_ptr = &writer;
86
+
87
+ // Encode
88
+ if (!WebPEncode(&config, &picture)) {
89
+ WebPMemoryWriterClear(&writer);
90
+ WebPPictureFree(&picture);
91
+ result.success = false;
92
+ result.errorMessage = std::string("WebP encoding failed: ") + std::to_string(picture.error_code);
93
+ return result;
94
+ }
95
+
96
+ // Write to file
97
+ std::ofstream outFile(outputPath, std::ios::binary);
98
+ if (!outFile.is_open()) {
99
+ WebPMemoryWriterClear(&writer);
100
+ WebPPictureFree(&picture);
101
+ result.success = false;
102
+ result.errorMessage = "Failed to open output file for writing";
103
+ return result;
104
+ }
105
+
106
+ outFile.write(reinterpret_cast<const char*>(writer.mem), writer.size);
107
+ if (!outFile.good()) {
108
+ WebPMemoryWriterClear(&writer);
109
+ WebPPictureFree(&picture);
110
+ result.success = false;
111
+ result.errorMessage = "Failed to write WebP data to file";
112
+ return result;
113
+ }
114
+ outFile.close();
115
+
116
+ // Set result
117
+ result.success = true;
118
+ result.sizeBytes = writer.size;
119
+
120
+ // Cleanup
121
+ WebPMemoryWriterClear(&writer);
122
+ WebPPictureFree(&picture);
123
+
124
+ return result;
125
+ #else
126
+ // Placeholder until libwebp is vendored
127
+ result.success = false;
128
+ result.errorMessage = "libwebp not yet integrated. Please vendor libwebp sources to cpp/vendor/libwebp/ and define WEBP_AVAILABLE.";
129
+
130
+ return result;
131
+ #endif
132
+ }
@@ -0,0 +1,41 @@
1
+ #ifndef IMAGE_TO_WEBP_H
2
+ #define IMAGE_TO_WEBP_H
3
+
4
+ #include <cstdint>
5
+ #include <string>
6
+
7
+ struct WebPEncodeOptions {
8
+ int quality = 80;
9
+ int method = 3;
10
+ bool lossless = false;
11
+ bool stripMetadata = true;
12
+ int threadLevel = 1;
13
+ bool exact = false;
14
+ };
15
+
16
+ struct WebPEncodeResult {
17
+ bool success = false;
18
+ std::string errorMessage;
19
+ uint32_t width = 0;
20
+ uint32_t height = 0;
21
+ size_t sizeBytes = 0;
22
+ };
23
+
24
+ /**
25
+ * Encode RGBA image data to WebP format.
26
+ *
27
+ * @param rgbaData Pointer to RGBA pixel data (width * height * 4 bytes)
28
+ * @param width Image width in pixels
29
+ * @param height Image height in pixels
30
+ * @param options Encoding options
31
+ * @param outputPath Path to write the WebP file
32
+ * @return Result containing success status, dimensions, and file size
33
+ */
34
+ WebPEncodeResult encodeWebP(
35
+ const uint8_t* rgbaData,
36
+ uint32_t width,
37
+ uint32_t height,
38
+ const WebPEncodeOptions& options,
39
+ const std::string& outputPath);
40
+
41
+ #endif // IMAGE_TO_WEBP_H
package/cpp/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # C++ Shared Code
2
+
3
+ This directory contains shared C++ code for WebP encoding using libwebp.
4
+
5
+ ## libwebp Vendor Setup
6
+
7
+ To set up libwebp:
8
+
9
+ 1. Download libwebp source (>= 1.6.0) from https://github.com/webmproject/libwebp
10
+ 2. Extract to `cpp/vendor/libwebp/`
11
+ 3. The build systems (CMake for Android, CocoaPods for iOS) will compile libwebp as a static library
12
+
13
+ ## Structure
14
+
15
+ - `ImageToWebP.h` / `ImageToWebP.cpp`: C++ wrapper around libwebp encoding API
16
+ - `vendor/libwebp/`: Vendored libwebp source (to be added)
17
+
18
+ ## Build Integration
19
+
20
+ - **Android**: CMakeLists.txt includes libwebp sources and links statically
21
+ - **iOS**: Podspec includes libwebp sources and compiles with the module
package/cpp/SETUP.md ADDED
@@ -0,0 +1,71 @@
1
+ # libwebp Setup Guide
2
+
3
+ This library uses [libwebp](https://github.com/webmproject/libwebp) (>= 1.6.0) for WebP encoding.
4
+
5
+ ## Quick Setup
6
+
7
+ **You only need to vendor libwebp sources - everything else is already configured!**
8
+
9
+ ### Option 1: Use the Script (Recommended)
10
+
11
+ **Windows (PowerShell):**
12
+ ```powershell
13
+ .\scripts\vendor-libwebp.ps1
14
+ ```
15
+
16
+ **macOS/Linux:**
17
+ ```bash
18
+ chmod +x scripts/vendor-libwebp.sh
19
+ ./scripts/vendor-libwebp.sh
20
+ ```
21
+
22
+ **Or via yarn:**
23
+ ```bash
24
+ yarn vendor:libwebp
25
+ ```
26
+
27
+ ### Option 2: Manual Setup
28
+
29
+ ```bash
30
+ cd cpp
31
+ git clone https://github.com/webmproject/libwebp.git vendor/libwebp
32
+ cd vendor/libwebp
33
+ git checkout v1.6.0
34
+ cd ../../..
35
+ ```
36
+
37
+ ## What Happens Next
38
+
39
+ Once libwebp is vendored:
40
+
41
+ 1. **Build files auto-detect libwebp** - Both iOS (`ReactNativeImageToWebp.podspec`) and Android (`CMakeLists.txt`) automatically detect and include libwebp sources if they exist at `cpp/vendor/libwebp/src`
42
+
43
+ 2. **C++ code is ready** - The implementation in `cpp/ImageToWebP.cpp` is already complete and will compile once libwebp is present (it's behind `#ifdef WEBP_AVAILABLE`)
44
+
45
+ 3. **Build flags are set** - Release flags (`-O3 -DNDEBUG`) and `WEBP_AVAILABLE` macro are automatically configured
46
+
47
+ ## Verification
48
+
49
+ After vendoring, verify libwebp is detected:
50
+
51
+ - **iOS**: Run `pod install` in `example/ios` - you should see libwebp sources included
52
+ - **Android**: Build will show "libwebp found, including sources" message
53
+
54
+ ## Current Status
55
+
56
+ ✅ Build files configured (auto-detect libwebp)
57
+ ✅ C++ implementation ready (behind `#ifdef WEBP_AVAILABLE`)
58
+ ✅ Compiler flags configured
59
+ ⏳ **You need to:** Vendor libwebp sources (run the script above)
60
+
61
+ ## Implementation Details
62
+
63
+ The `cpp/ImageToWebP.cpp` file uses libwebp's advanced API:
64
+
65
+ 1. `WebPConfigInit()` and `WebPConfigPreset()` for configuration
66
+ 2. `WebPPictureInit()` and `WebPPictureImportRGBA()` for image data
67
+ 3. `WebPMemoryWriter` for output buffering
68
+ 4. `WebPEncode()` for encoding
69
+ 5. Proper cleanup of all libwebp structures
70
+
71
+ See libwebp documentation: https://developers.google.com/speed/webp/docs/api
@@ -0,0 +1,5 @@
1
+ #import <ReactNativeImageToWebpSpec/ReactNativeImageToWebpSpec.h>
2
+
3
+ @interface ReactNativeImageToWebp : NSObject <NativeReactNativeImageToWebpSpec>
4
+
5
+ @end