deer social fork for personal usage. but you might see a use idk. github mirror
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

improves image compression further

ayla eaf84b53 5dc63307

+91 -79
-1
src/lib/api/resolve.ts
··· 221 221 uri: imageUri, 222 222 width: POST_IMG_MAX.width, 223 223 height: POST_IMG_MAX.height, 224 - mode: 'contain', 225 224 maxSize: POST_IMG_MAX.size, 226 225 timeout: 15e3, 227 226 })
+4 -7
src/lib/media/manip.ts
··· 35 35 const resizedImage = await doResize(normalizePath(img.path), { 36 36 width: img.width, 37 37 height: img.height, 38 - mode: 'stretch', 39 38 maxSize, 40 39 }) 41 40 const finalImageMovedPath = await moveToPermanentPath( 42 41 resizedImage.path, 43 - '.webp', 42 + '.jpeg', 44 43 ) 45 44 const finalImg = { 46 45 ...resizedImage, ··· 53 52 uri: string 54 53 width: number 55 54 height: number 56 - mode: 'contain' | 'cover' | 'stretch' 57 55 maxSize: number 58 56 timeout: number 59 57 } ··· 181 179 interface DoResizeOpts { 182 180 width: number 183 181 height: number 184 - mode: 'contain' | 'cover' | 'stretch' 185 182 maxSize: number 186 183 } 187 184 ··· 199 196 width: imageRes.width, 200 197 height: imageRes.height, 201 198 }) 202 - const originalSize = getDataUriSize(localUri) + 1024 199 + const originalSize = getDataUriSize(localUri) 203 200 204 201 let maxQualityPercentage = 110 // exclusive 205 202 let newDataUri ··· 211 208 localUri, 212 209 [{resize: newDimensions}], 213 210 { 214 - format: SaveFormat.WEBP, 211 + format: SaveFormat.JPEG, 215 212 compress: qualityPercentage / 100, 216 213 }, 217 214 ) ··· 228 225 if (fileInfo.size < opts.maxSize && fileInfo.size < originalSize) { 229 226 newDataUri = { 230 227 path: normalizePath(resizeRes.uri), 231 - mime: 'image/webp', 228 + mime: 'image/jpeg', 232 229 size: fileInfo.size, 233 230 width: resizeRes.width, 234 231 height: resizeRes.height,
+38 -23
src/lib/media/manip.web.ts
··· 1 1 /// <reference lib="dom" /> 2 2 3 + import {encode} from '@jsquash/webp' 4 + 3 5 import {type PickerImage} from './picker.shared' 4 6 import {type Dimensions} from './types' 5 7 import {blobToDataUri, getDataUriSize} from './util' ··· 15 17 return await doResize(img.path, { 16 18 width: img.width, 17 19 height: img.height, 18 - mode: 'stretch', 19 20 maxSize, 20 21 }) 21 22 } ··· 24 25 uri: string 25 26 width: number 26 27 height: number 27 - mode: 'contain' | 'cover' | 'stretch' 28 28 maxSize: number 29 29 timeout: number 30 30 } ··· 83 83 interface DoResizeOpts { 84 84 width: number 85 85 height: number 86 - mode: 'contain' | 'cover' | 'stretch' 87 86 maxSize: number 88 87 } 89 88 ··· 91 90 dataUri: string, 92 91 opts: DoResizeOpts, 93 92 ): Promise<PickerImage> { 94 - const originalSize = getDataUriSize(dataUri) + 1024 93 + const originalSize = getDataUriSize(dataUri) 95 94 96 95 let newDataUri 97 96 98 97 let maxQualityPercentage = 110 //exclusive 99 98 let finalCompressionPercentage: number = 100 100 99 100 + const imageData = await createResizedImage(dataUri, { 101 + width: opts.width, 102 + height: opts.height, 103 + }) 104 + 101 105 while (maxQualityPercentage > 1) { 102 106 const qualityPercentage = Math.round(maxQualityPercentage - 10) 103 - const tempDataUri = await createResizedImage(dataUri, { 104 - width: opts.width, 105 - height: opts.height, 106 - quality: qualityPercentage / 100, 107 - mode: opts.mode, 108 - }) 107 + 108 + const [tempDataUri, size] = await createCompressedImage( 109 + imageData, 110 + qualityPercentage, 111 + ) 109 112 110 - const size = getDataUriSize(tempDataUri) 111 - if (size < opts.maxSize && size < originalSize) { 113 + console.log(size, qualityPercentage, originalSize) 114 + if (size <= opts.maxSize && size <= originalSize) { 112 115 newDataUri = tempDataUri 113 116 finalCompressionPercentage = qualityPercentage 114 117 break ··· 135 138 { 136 139 width, 137 140 height, 138 - quality, 139 - mode, 140 141 }: { 141 142 width: number 142 143 height: number 143 - quality: number 144 - mode: 'contain' | 'cover' | 'stretch' 145 144 }, 146 - ): Promise<string> { 145 + ): Promise<ImageData> { 147 146 return new Promise((resolve, reject) => { 148 147 const img = document.createElement('img') 149 - img.addEventListener('load', () => { 148 + img.addEventListener('load', async () => { 150 149 const canvas = document.createElement('canvas') 151 150 const ctx = canvas.getContext('2d') 152 151 if (!ctx) { 153 152 return reject(new Error('Failed to resize image')) 154 153 } 155 - 156 154 let scale = 1 157 - if (mode === 'cover') { 155 + if (img.width > width || img.height > height) { 158 156 scale = img.width < img.height ? width / img.width : height / img.height 159 - } else if (mode === 'contain') { 160 - scale = img.width > img.height ? width / img.width : height / img.height 161 157 } 162 158 let w = img.width * scale 163 159 let h = img.height * scale ··· 166 162 canvas.height = h 167 163 168 164 ctx.drawImage(img, 0, 0, w, h) 169 - resolve(canvas.toDataURL('image/webp', quality)) 165 + 166 + const imageData = ctx.getImageData(0, 0, img.width, img.height) 167 + 168 + resolve(imageData) 170 169 }) 171 170 img.addEventListener('error', ev => { 172 171 reject(ev.error) 173 172 }) 174 173 img.src = dataUri 175 174 }) 175 + } 176 + 177 + async function createCompressedImage( 178 + imageData: ImageData, 179 + quality: number, 180 + ): Promise<[string, number]> { 181 + const webpBuffer = await encode(imageData, { 182 + lossless: quality === 100 ? 1 : 0, 183 + quality: quality || 100, 184 + method: 4, 185 + }) 186 + 187 + const size = webpBuffer.byteLength 188 + 189 + const blob = new Blob([webpBuffer], {type: 'image/webp'}) 190 + return [URL.createObjectURL(blob), size] 176 191 } 177 192 178 193 export async function saveBytesToDisk(
+16 -25
src/state/gallery.ts
··· 11 11 manipulateAsync, 12 12 SaveFormat, 13 13 } from 'expo-image-manipulator' 14 - import { 15 - type ImageResult, 16 - type SaveOptions, 17 - } from 'expo-image-manipulator/src/ImageManipulator.types' 14 + import {type SaveOptions} from 'expo-image-manipulator/src/ImageManipulator.types' 18 15 import {nanoid} from 'nanoid/non-secure' 19 16 20 17 import {POST_IMG_MAX} from '#/lib/constants' ··· 198 195 const [w, h] = containImageRes(source.width, source.height, POST_IMG_MAX) 199 196 200 197 let maxQualityPercentage = 201 - 110 - (originalSize >= POST_IMG_MAX.size * 2 ? 10 : 0) // exclusive 198 + 110 - (originalSize >= POST_IMG_MAX.size * 2 ? 10 : 0) 202 199 let newDataUri 203 200 201 + const resizedImage = await manipulateAsync( 202 + source.path, 203 + [{resize: {width: w, height: h}}], 204 + {format: SaveFormat.PNG}, 205 + ) 206 + 204 207 while (maxQualityPercentage > 1) { 205 208 const qualityPercentage = Math.round(maxQualityPercentage - 10) 206 209 207 - const res = await manipulateWebp( 208 - source.path, 209 - {resize: {width: w, height: h}}, 210 - { 211 - compress: qualityPercentage, 212 - format: SaveFormat.WEBP, 213 - }, 214 - ) 210 + const res = await manipulateWebp(resizedImage.uri, { 211 + compress: qualityPercentage, 212 + format: SaveFormat.WEBP, 213 + }) 215 214 216 215 if (res.size <= POST_IMG_MAX.size && res.size <= originalSize) { 217 216 newDataUri = { 218 217 path: await moveIfNecessary(res.uri), 219 - width: res.width, 220 - height: res.height, 218 + width: w, 219 + height: h, 221 220 mime: 'image/webp', 222 221 size: res.size, 223 222 quality: qualityPercentage, ··· 237 236 238 237 export const manipulateWebp = async ( 239 238 uri: string, 240 - resize: {resize: {width: number; height: number}} = { 241 - resize: {width: 128, height: 128}, 242 - }, 243 239 saveOptions: SaveOptions = {}, 244 - ): Promise<ImageResult & {size: number}> => { 245 - const resized = await manipulateAsync(uri, [resize], { 246 - format: SaveFormat.PNG, 247 - }) 240 + ): Promise<{uri: string; size: number}> => { 248 241 const tempOut = (await getTemporaryImageFile()) as string 249 242 250 - const resultUri = await WebP.convertImage(resized.uri, tempOut, { 243 + const resultUri = await WebP.convertImage(uri, tempOut, { 251 244 type: saveOptions.compress === 100 ? WebP.Type.LOSSLESS : WebP.Type.LOSSY, 252 245 quality: saveOptions.compress || 100, 253 246 }) ··· 256 249 257 250 return { 258 251 uri: resultUri, 259 - width: resize.resize.width, 260 - height: resize.resize.height, 261 252 size: blob.size, 262 253 } 263 254 }
+32 -21
src/state/gallery.web.ts
··· 13 13 import {getImageDim} from '#/lib/media/manip' 14 14 import {type PickerImage} from '#/lib/media/picker.shared' 15 15 import {getDataUriSize} from '#/lib/media/util' 16 - import {resize} from '../../node_modules/expo-image-manipulator/src/web/actions/index.web' 17 16 18 17 export type ImageTransformation = { 19 18 crop?: ActionCrop['crop'] ··· 154 153 155 154 export async function compressImage(img: ComposerImage): Promise<PickerImage> { 156 155 const source = img.transformed || img.source 157 - const originalSize = getDataUriSize(img.source.path) 156 + // i should probably go and turn it into base64 and whatever but nah who cares 157 + const originalSize = img.source.path.startsWith('blob:') 158 + ? POST_IMG_MAX.size 159 + : getDataUriSize(img.source.path) 158 160 159 161 const [w, h] = containImageRes(source.width, source.height, POST_IMG_MAX) 160 162 ··· 162 164 110 - (originalSize >= POST_IMG_MAX.size * 2 ? 10 : 0) 163 165 let newDataUri 164 166 167 + const resizedImage = await loadAndResizeImage(source.path, { 168 + width: w, 169 + height: h, 170 + }) 171 + 165 172 while (maxQualityPercentage > 1) { 166 173 const qualityPercentage = Math.round(maxQualityPercentage - 10) 167 174 168 - const res = await manipulateWebp(source.path, { 175 + const res = await manipulateWebp(resizedImage, { 169 176 compress: qualityPercentage, 170 177 format: SaveFormat.WEBP, 171 - resize: {width: w, height: h}, 172 178 }) 173 179 174 180 if (res.size <= POST_IMG_MAX.size && res.size <= originalSize) { ··· 193 199 throw new Error(`Unable to compress image`) 194 200 } 195 201 196 - export const manipulateWebp = async ( 202 + const loadAndResizeImage = async ( 197 203 uri: string, 198 - saveOptions: SaveOptions & { 199 - resize?: {width: number; height: number} 200 - method?: number 201 - } = {}, 202 - ): Promise<ImageResult & {size: number}> => { 204 + size: {width: number; height: number}, 205 + ) => { 203 206 const img = document.createElement('img') 204 207 img.src = uri 205 208 await new Promise(resolve => { 206 209 img.onload = resolve 207 210 }) 208 211 const canvas = document.createElement('canvas') 209 - ;[canvas.width, canvas.height] = [img.width, img.height] 210 212 const ctx = canvas.getContext('2d') as CanvasRenderingContext2D 211 - ctx.drawImage(img, 0, 0) 212 213 213 - if (saveOptions.resize) { 214 - resize(canvas, saveOptions.resize) 215 - } 214 + let scale = 215 + img.width < img.height ? size.width / img.width : size.height / img.height 216 + let w = img.width * scale 217 + let h = img.height * scale 218 + 219 + canvas.width = w 220 + canvas.height = h 216 221 217 - const rawImageData = ctx.getImageData(0, 0, img.width, img.height) 222 + ctx.drawImage(img, 0, 0, w, h) 218 223 219 - const webpBuffer = await encode(rawImageData, { 224 + return ctx.getImageData(0, 0, img.width, img.height) 225 + } 226 + 227 + export const manipulateWebp = async ( 228 + imageData: ImageData, 229 + saveOptions: SaveOptions = {}, 230 + ): Promise<ImageResult & {size: number}> => { 231 + const webpBuffer = await encode(imageData, { 220 232 lossless: saveOptions.compress === 100 ? 1 : 0, 221 233 quality: saveOptions.compress || 100, 222 - method: saveOptions.method || 4, 234 + method: 4, 223 235 }) 224 236 225 237 const blob = new Blob([webpBuffer], {type: 'image/webp'}) 226 238 const resultUri = URL.createObjectURL(blob) 227 - 228 239 return { 229 240 uri: resultUri, 230 - width: rawImageData.width, 231 - height: rawImageData.height, 241 + width: imageData.width, 242 + height: imageData.height, 232 243 size: blob.size, 233 244 } 234 245 }
-1
src/view/com/composer/text-input/TextInput.tsx
··· 96 96 uri: feature.uri, 97 97 width: POST_IMG_MAX.width, 98 98 height: POST_IMG_MAX.height, 99 - mode: 'contain', 100 99 maxSize: POST_IMG_MAX.size, 101 100 timeout: 15e3, 102 101 })
+1 -1
src/view/com/modals/CropImage.web.tsx
··· 61 61 ], 62 62 { 63 63 base64: true, 64 - format: SaveFormat.WEBP, 64 + format: SaveFormat.PNG, 65 65 }, 66 66 ) 67 67