Skip to content

Instantly share code, notes, and snippets.

@Grohden
Last active January 21, 2024 00:16
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 Grohden/0f813f150792af1464776c0a9c1227b1 to your computer and use it in GitHub Desktop.
Save Grohden/0f813f150792af1464776c0a9c1227b1 to your computer and use it in GitHub Desktop.
My "port" fo the Vibrant lib for react native
import VibrantCore from '@vibrant/core';
import { pipeline } from './pipeline.ts';
import { ReactNativeImage } from './react-native-image.ts';
export const Vibrant = VibrantCore;
// Don't forget to add this to your app root:
// useEffect(() => {
// globalSetupVibrantEffect();
// }, []);
const globalSetupVibrantEffect = () => {
VibrantCore.DefaultOpts.quantizer = 'mmcq';
VibrantCore.DefaultOpts.generators = ['default'];
VibrantCore.DefaultOpts.filters = ['default'];
VibrantCore.DefaultOpts.ImageClass = ReactNativeImage;
VibrantCore.use(pipeline);
};

This implementation is incomplete: only works correctly (I think) for jpg files, we still need to find out a simplified version of jimp that does not import anything from node

To use this impl, add these packages and copy the files to a folder

"@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/generator-default": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",

Example usage:

My implementation works for this hook:

import { Palette } from '@vibrant/color';
import { useEffect, useState } from 'react';

// Files of this gist:
import { Vibrant } from '../react-native-vibrant';

export const useImagePalette = (url: string) => {
  const [colors, setColors] = useState<Palette>();

  useEffect(() => {
    if (!url) {
      return;
    }

    let mounted = true;

    fetch(url)
      .then((response) => response.arrayBuffer())
      .then((buffer) => Vibrant.from(buffer).getPalette())
      .then((palette) => {
        mounted && setColors(palette);
      })
      .catch((err) => {
        console.log(err);
      });

    return () => {
      mounted = false;
    };
  }, [url]);

  return colors;
};
const SOI = 0xD8;
const EOI = 0xD9;
const TEM = 0x01;
const SOF = 0xC0;
// Port of https://stackoverflow.com/a/48488655
export const getJPGSizes = (buffer: ArrayBuffer) => {
const dataView = new DataView(buffer);
const UInt8At = (n: number) => dataView.getUint8(n);
// FIXME: prob need to check the JPG bytes
// to make sure this is a JPG buffer
let offset = 0;
while (offset < buffer.byteLength) {
while (UInt8At(offset) === 0xff) {
offset++;
}
const marker = UInt8At(offset);
offset++;
if (marker === SOI) {
continue;
}
if (marker === EOI) {
break;
}
if (0XD0 <= marker && marker <= 0XD7) {
continue;
}
if (marker === TEM) {
continue;
}
const len = (UInt8At(offset) << 8) | UInt8At(offset + 1);
offset += 2;
if (marker == SOF) {
return {
bpc: UInt8At(offset), // precision (bits per channel)
h: (UInt8At(offset + 1) << 8) | UInt8At(offset + 2),
w: (UInt8At(offset + 3) << 8) | UInt8At(offset + 4),
cps: UInt8At(offset + 5), // number of color components
};
}
offset += len - 2;
}
return null;
};
import DefaultGenerator from '@vibrant/generator-default';
import MMCQ from '@vibrant/quantizer-mmcq';
import { BasicPipeline } from '@vibrant/core/lib/pipeline';
export const pipeline = new BasicPipeline()
.filter.register(
'default',
(r, g, b, a) => a >= 125 && !(r > 250 && g > 250 && b > 250),
)
.quantizer.register('mmcq', MMCQ)
.generator.register('default', DefaultGenerator);
import { ImageBase } from '@vibrant/image';
import { getJPGSizes } from './jpg-size.ts';
export class ReactNativeImage extends ImageBase {
private image: ArrayBuffer | null = null;
private width: number = 0;
private height: number = 0;
async load(image: unknown) {
this.image = image as ArrayBuffer;
const dimens = getJPGSizes(this.image);
this.height = dimens?.h || 0;
this.width = dimens?.w || 0;
return this;
}
clear() {}
update(): void {}
getWidth() {
return this.width;
// return this.image!.bitmap.width;
}
getHeight() {
return this.height;
}
resize(targetWidth: number, targetHeight: number): void {
// FIXME: find a lib that can resize images in rn
// this.image!.resize(targetWidth, targetHeight);
}
getPixelCount(): number {
return this.getWidth() * this.getHeight();
}
getImageData() {
return {
data: new Uint8ClampedArray(this.image!),
width: this.getWidth(),
height: this.getHeight(),
};
// return this.image!.bitmap;
}
remove(): void {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment