Commit f47e1f16 authored by Brendan Wong's avatar Brendan Wong Committed by GitHub

fix: color extraction for rich link previews (#7138)

* feat: add token and nft injection

* feat: basic tests

* fix: get jest configured properly

* fix: change timeout

* fix: uninstall port ready

* fix: readd port ready

* fix: local tests work

* Update yarn.lock

* add lint disable for setup files

* fix: update dependencies

* fix: basic test suite for nfts/tokens

* feat: collection data

* fix: make tests more comprehensive

* fix: change matches to contains

* fix: tests for twitter alt image tag

* fix: image gen

* fix: add patch-package

* fix: update yarn install

* feat: basic image gen for nfts and collections

* fix: remove vibrant attempt

* use watermark asset

* dynamically grab color

* modularize code and prototype for token preview

* refactor code

* finalize css

* fix color grabber

* update tests

* fix up css

* refactor code a bit more

* remove console logs

* tests

* update tests

* update images based on design feedback

* network logos

* update lint

* slight refactoring

* more refactoring

* fix packages

* Update yarn.lock

* remove dynamically generated image stuff

* Revert "remove dynamically generated image stuff"

This reverts commit a80241edb3a970a724b9a07ce36e492ff8a1c2af.

* change image reference and revamp tests

* cleanup return values

* Create README.md

* Revert "Create README.md"

This reverts commit 7a91c98d384995fba914c9bf9a2fb3072793621f.

* First round of feedback

* comments

* feat: cache

* Update test.yml

* Update test.yml

* Update test.yml

* feedback round 2

* final feedback

* final final feedback

* add coverage and other options

* Update test.yml

* start typecheck

* update cache

* update snapshots?

* Update jest.config.json

* Update jest.config.json

* give timeout some buffer

* update import

* upgrade ts

* fix typing for apollo deps

* finalize typechecks

* downgrade typescript to original version

* add cache directory to jest

* remove coverage

* remove google analytics from tests

* merge main

* remove timeout

* update tests

* update graphql queries

* review changes

* try cache setup

* Update cache.test.ts

* make cache helper function

* cache test

* remove unneeded test causing issues

* feat: parallelize cache (#6930)

* feat: parallelize cache?

* remove graph query from concurrency await

* most of feedback

* move tests

* update token tests

* singleton cache

* restructuring res and cache promise

* abstract away repeated graph logic

* update tests and functions

* refactor

* update typing, parallelize, and start tests

* fix one tsc issue

* final feedback

* Update yarn.lock

* final final feedback

* add svgs

* try and setup svg

* stashing changes

* cleanup!

* prepare for start of feedback?

* LESS GOO

* modify versioning

* fix: update wrangler version

* Update yarn.lock

* downgrade wrangler

* Update yarn.lock

* Update yarn.lock

* fix type error

* update github test

* cleanup tests

* Delete custom.d.ts

* fix: cloudfunctions

* update tests

* final touchups

* lint

* change github action

* Update yarn.lock

* styling updates

* nate's feedback

* feedback p1

* typing feedback

* update yarn

* Create wrangler.toml

* move wrangler.toml location

* last try

* Delete wrangler.toml

* use 2.20?

* remove comment

* Update yarn.lock

* change compatibility date

* update wrangler and fix bugs

* Update colorthief+2.4.0.patch

* build: cleanup flags

* cleaner patches

* update compatibility date

* quick tweeks

* cleanup rendering and lint

* patch things up

* fix: color extraction

* DONE!

* tests and other qol updates

* lint

* add more tests

* feedback

* simplify getcolors

---------
Co-authored-by: default avatarZach Pomerantz <zzmp@uniswap.org>
parent 9954f950
......@@ -11,8 +11,7 @@ test.each(assetImageUrl)('assetImageUrl', async (url) => {
const invalidAssetImageUrl = [
'http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/100000',
'http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544',
'http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c545',
'http://127.0.0.1:3000/api/image/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/44700',
]
test.each(invalidAssetImageUrl)('invalidAssetImageUrl', async (url) => {
......
......@@ -21,8 +21,7 @@ export const onRequest: PagesFunction = async ({ params, request }) => {
const data = await getRequest(
cacheUrl,
() => getToken(networkName, tokenAddress, cacheUrl),
(data): data is NonNullable<Awaited<ReturnType<typeof getToken>>> =>
Boolean(data.symbol && data.ogImage && data.name)
(data): data is NonNullable<Awaited<ReturnType<typeof getToken>>> => Boolean(data.symbol && data.name)
)
if (!data) {
......@@ -69,7 +68,7 @@ export const onRequest: PagesFunction = async ({ params, request }) => {
color: 'white',
}}
>
{data.ogImage != '' ? (
{data.ogImage ? (
<img src={data.ogImage} width="144px" style={{ borderRadius: '100%' }}>
{networkLogo != '' && (
<img
......
import { DEFAULT_COLOR } from '../constants'
import getColor from './getColor'
test('should return the average color of a black PNG image', async () => {
const image = 'https://static.vecteezy.com/system/resources/previews/001/209/957/original/square-png.png'
const color = await getColor(image)
expect(color).toEqual([0, 0, 0])
})
test('should return the average color of a blue PNG image', async () => {
const image = 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTB2Ztcim-RKbOu57kfjYpXnnS1MO5YMUaUH9Lk5Eg&s'
const color = await getColor(image)
expect(color).toEqual([2, 6, 251])
})
test('should return the average color of a white PNG image', async () => {
const image = 'https://www.cac.cornell.edu/wiki/images/4/44/White_square.png'
const color = await getColor(image)
expect(color).toEqual([255, 255, 255])
})
test('should return the average color of a black JPG image', async () => {
const image =
'https://imageio.forbes.com/specials-images/imageserve/5ed6636cdd5d320006caf841/0x0.jpg?format=jpg&width=1200'
const color = await getColor(image)
expect(color).toEqual([0, 0, 0])
})
test('should return default color for a gif image', async () => {
const image = 'https://thumbs.gfycat.com/AgitatedLiveAgouti-size_restricted.gif'
const color = await getColor(image)
expect(color).toEqual(DEFAULT_COLOR)
})
import ColorThief from 'colorthief/src/color-thief-node'
import { Buffer } from 'buffer'
import JPEG from 'jpeg-js'
import PNG from 'png-ts'
import { DEFAULT_COLOR } from '../constants'
export default async function getColor(image: string) {
export default async function getColor(image: string | undefined) {
if (!image) {
return DEFAULT_COLOR
}
try {
const data = await fetch(image)
const buffer = await data.arrayBuffer()
const arrayBuffer = Buffer.from(buffer)
const palette = await ColorThief.getPalette(arrayBuffer, 5)
return palette[0] ?? DEFAULT_COLOR
const type = data.headers.get('content-type') ?? ''
return getAverageColor(arrayBuffer, type)
} catch (e) {
return DEFAULT_COLOR
}
}
function getAverageColor(arrayBuffer: Uint8Array, type?: string) {
let pixels
switch (type) {
case 'image/png': {
const image = PNG.load(arrayBuffer)
pixels = image.decode()
break
}
case 'image/jpeg' || 'image/jpg': {
const jpeg = JPEG.decode(arrayBuffer, { useTArray: true })
pixels = jpeg.data
break
}
default: {
return DEFAULT_COLOR
}
}
const pixelCount = pixels.length / 4
let transparentPixels = 0
let r = 0
let g = 0
let b = 0
for (let i = 0; i < pixelCount; i++) {
if (pixels[i * 4 + 3] === 0) {
transparentPixels++
continue
}
r += pixels[i * 4]
g += pixels[i * 4 + 1]
b += pixels[i * 4 + 2]
}
r = Math.floor(r / (pixelCount - transparentPixels))
g = Math.floor(g / (pixelCount - transparentPixels))
b = Math.floor(b / (pixelCount - transparentPixels))
return [r, g, b]
}
......@@ -50,7 +50,7 @@ export default async function getToken(networkName: string, tokenAddress: string
image,
url,
symbol: asset.symbol ?? 'UNK',
ogImage: asset.project?.logoUrl ?? '',
ogImage: asset.project?.logoUrl,
name: asset.name ?? 'Token',
}
return formattedAsset
......
diff --git a/node_modules/get-pixels/dom-pixels.js b/node_modules/get-pixels/dom-pixels.js
index 7714528..64e8db3 100644
--- a/node_modules/get-pixels/dom-pixels.js
+++ b/node_modules/get-pixels/dom-pixels.js
@@ -1,10 +1,8 @@
'use strict'
-var path = require('path')
+var extname = require('ext-name')
var ndarray = require('ndarray')
var GifReader = require('omggif').GifReader
-var pack = require('ndarray-pack')
-var through = require('through')
var parseDataURI = require('data-uri-to-buffer')
function defaultImage(url, cb) {
@@ -117,9 +115,9 @@ module.exports = function getPixels(url, type, cb) {
cb = type
type = ''
}
- var ext = path.extname(url)
+ var ext = extname(url).ext
switch(type || ext.toUpperCase()) {
- case '.GIF':
+ case 'GIF':
httpGif(url, cb)
break
default:
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment