Skip to content

Instantly share code, notes, and snippets.

@mjm918
Created July 4, 2022 07:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mjm918/93e1327b7bd8e6e10b5be735a541cf57 to your computer and use it in GitHub Desktop.
Save mjm918/93e1327b7bd8e6e10b5be735a541cf57 to your computer and use it in GitHub Desktop.
React-Native Image Swatches / Color
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());
}
}
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'
}
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