Created
July 4, 2022 07:40
-
-
Save mjm918/93e1327b7bd8e6e10b5be735a541cf57 to your computer and use it in GitHub Desktop.
React-Native Image Swatches / Color
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import android.graphics.Bitmap; | |
import android.graphics.BitmapFactory; | |
import android.graphics.Color; | |
import android.util.Base64; | |
import androidx.annotation.NonNull; | |
import androidx.palette.graphics.Palette; | |
import com.bumptech.glide.Glide; | |
import com.bumptech.glide.load.engine.DiskCacheStrategy; | |
import com.facebook.react.bridge.Arguments; | |
import com.facebook.react.bridge.Promise; | |
import com.facebook.react.bridge.ReactApplicationContext; | |
import com.facebook.react.bridge.ReactContextBaseJavaModule; | |
import com.facebook.react.bridge.ReactMethod; | |
import com.facebook.react.bridge.ReadableMap; | |
import com.facebook.react.bridge.WritableArray; | |
import com.facebook.react.bridge.WritableMap; | |
import java.net.MalformedURLException; | |
import java.net.URI; | |
import java.net.URISyntaxException; | |
import java.net.URL; | |
import java.util.List; | |
import java.util.ListIterator; | |
import java.util.Locale; | |
import java.util.concurrent.ExecutionException; | |
public class ImageColorsModule extends ReactContextBaseJavaModule { | |
public static final String MODULE_NAME = "ImageColors"; | |
private static final String BASE64_SCHEME = "data"; | |
private final ReactApplicationContext reactContext; | |
public ESImageColorsModule(ReactApplicationContext reactContext) { | |
super(reactContext); | |
this.reactContext = reactContext; | |
} | |
@NonNull | |
@Override | |
public String getName() { | |
return ESImageColorsModule.MODULE_NAME; | |
} | |
private String intToRGBA(int color) { | |
return String.format(Locale.ROOT,"rgb(%d,%d,%d)", Color.red(color), Color.green(color), Color.blue(color)); | |
} | |
private WritableMap convertSwatch(Palette.Swatch swatch) { | |
if (swatch == null) { | |
return null; | |
} | |
WritableMap swatchMap = Arguments.createMap(); | |
swatchMap.putString("average", intToRGBA(swatch.getRgb())); | |
swatchMap.putInt("population", swatch.getPopulation()); | |
swatchMap.putString("vibrant", intToRGBA(swatch.getTitleTextColor())); | |
swatchMap.putString("darkVibrant", intToRGBA(swatch.getBodyTextColor())); | |
swatchMap.putString("swatchInfo", swatch.toString()); | |
return swatchMap; | |
} | |
private Palette getPallet(Bitmap bitmap, final Promise promise) { | |
if (bitmap == null) { | |
promise.reject("Error","Bitmap Null"); | |
return null; | |
} else if (bitmap.isRecycled()) { | |
promise.reject("Error","Bitmap Recycled"); | |
return null; | |
} | |
return Palette.from(bitmap).generate(); | |
} | |
@ReactMethod | |
public void getColors(String source, Promise promise) { | |
Context context = getReactApplicationContext(); | |
int resourceId = context.getResources().getIdentifier(source, "drawable", context.getPackageName()); | |
Bitmap image = null; | |
if (resourceId == 0){ | |
if (source.startsWith(ESImageColorsModule.BASE64_SCHEME)){ | |
String[] parts = source.split(","); | |
String base64Uri = parts[1]; | |
byte[] decodedString = Base64.decode(base64Uri, Base64.DEFAULT); | |
image = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length); | |
} else { | |
try{ | |
try{ | |
try { | |
try { | |
// download image with glide | |
URL url = new URL(source); | |
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef()); | |
image = Glide | |
.with(getReactApplicationContext()) | |
.asBitmap() | |
.skipMemoryCache(true) | |
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) | |
.load(uri.toURL()) | |
.submit() | |
.get(); | |
} catch (InterruptedException interruptedException){ | |
this.onException(interruptedException,promise); | |
} | |
} catch (ExecutionException executionException){ | |
this.onException(executionException,promise); | |
} | |
} catch (URISyntaxException uriSyntaxException){ | |
this.onException(uriSyntaxException,promise); | |
} | |
} catch (MalformedURLException urlException){ | |
this.onException(urlException,promise); | |
} | |
} | |
} else { | |
image = BitmapFactory.decodeResource(context.getResources(), resourceId); | |
} | |
if (image == null){ | |
this.onException(new Exception("Image is null"), promise); | |
} else { | |
Palette palette = this.getPallet(image, promise); | |
image.recycle(); | |
image = null; | |
if (palette == null){ | |
this.onException(new Exception("Palette is null"), promise); | |
} else { | |
WritableArray jsSwatches = Arguments.createArray(); | |
List<Palette.Swatch> swatches = palette.getSwatches(); | |
for (Palette.Swatch swatch : swatches) { | |
jsSwatches.pushMap(convertSwatch(swatch)); | |
} | |
promise.resolve(jsSwatches); | |
} | |
} | |
} | |
private void onException(Exception e, Promise promise) { | |
e.printStackTrace(); | |
promise.reject("Error", "ImageColors: " + e.getMessage()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
dependencies { | |
implementation 'androidx.palette:palette:1.0.0@aar' | |
implementation 'com.github.bumptech.glide:glide:4.13.0' | |
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0' | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
// Credit: https://github.com/osamaqarem/react-native-image-colors/blob/master/ios/ImageColors.swift | |
@objc(ImageColors) | |
class ImageColors: NSObject { | |
private let fallback = "#000" | |
enum ERRORS { | |
static let ERROR_1 = "Invalid URL"; | |
static let ERROR_2 = "Could not download image."; | |
static let ERROR_3 = "Could not parse image."; | |
} | |
private func getQuality(qualityOption: String) -> UIImageColorsQuality { | |
switch qualityOption { | |
case "lowest": return UIImageColorsQuality.lowest | |
case "low": return UIImageColorsQuality.low | |
case "high": return UIImageColorsQuality.high | |
case "highest": return UIImageColorsQuality.highest | |
default: return UIImageColorsQuality.low | |
} | |
} | |
private func toHexString(color: UIColor) -> String { | |
let comp = color.cgColor.components; | |
let r: CGFloat = comp![0] | |
let g: CGFloat = comp![1] | |
let b: CGFloat = comp![2] | |
let rgb: Int = (Int)(r * 255) << 16 | (Int)(g * 255) << 8 | (Int)(b * 255) << 0 | |
return String(format: "#%06X", rgb) | |
} | |
@objc | |
func getColors(_ uri: String, config: NSDictionary, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { | |
let defColor = config.value(forKey: "defaultColor") as? String | |
guard let parsedUri = URL(string: uri) else { | |
let error = NSError.init(domain: ImageColors.ERRORS.ERROR_1, code: -1) | |
reject("Error", ImageColors.ERRORS.ERROR_1, error) | |
return | |
} | |
var request = URLRequest(url: parsedUri) | |
if let headers = config.value(forKey: "headers") as? NSDictionary { | |
let allKeys = headers.allKeys | |
allKeys.forEach { (key) in | |
let key = key as! String | |
let value = headers.value(forKey: key) as? String | |
request.setValue(value, forHTTPHeaderField: key) | |
} | |
} | |
URLSession.shared.dataTask(with: request) { [unowned self] (data, response, error) in | |
guard let data = data, error == nil else { | |
reject("Error", ImageColors.ERRORS.ERROR_2, error) | |
return | |
} | |
guard let uiImage = UIImage(data: data) else { | |
let error = NSError.init(domain: ImageColors.ERRORS.ERROR_3, code: -3) | |
reject("Error", ImageColors.ERRORS.ERROR_3, error) | |
return | |
} | |
let qualityProp = config["quality"] as? String ?? "low" | |
let quality = getQuality(qualityOption: qualityProp) | |
uiImage.getColors(quality: quality) { colors in | |
var resultDict: Dictionary<String, String> = ["platform": "ios"] | |
if let background = colors?.background { | |
resultDict["background"] = self.toHexString(color: background) | |
} else { | |
resultDict["background"] = defColor ?? self.fallback | |
} | |
if let primary = colors?.primary { | |
resultDict["primary"] = self.toHexString(color: primary) | |
} else { | |
resultDict["primary"] = defColor ?? self.fallback | |
} | |
if let secondary = colors?.secondary { | |
resultDict["secondary"] = self.toHexString(color: secondary) | |
} else { | |
resultDict["secondary"] = defColor ?? self.fallback | |
} | |
if let detail = colors?.detail { | |
resultDict["detail"] = self.toHexString(color: detail) | |
} else { | |
resultDict["detail"] = defColor ?? self.fallback | |
} | |
resolve(resultDict) | |
} | |
}.resume() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment