about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2022-12-26 12:01:40 -0600
committerGitHub <noreply@github.com>2022-12-26 12:01:40 -0600
commit838fc601c1f89f028862212d169aebe4163c8672 (patch)
treee00dd942ea0b07f1560e4580c6f273a746c4808c
parent8652b74a38f67e7f88890c9c3eb3be090b53462c (diff)
downloadvoidsky-838fc601c1f89f028862212d169aebe4163c8672.tar.zst
Start with highest quality compression and find a suitable size (#33)
-rw-r--r--__tests__/lib/images.test.ts (renamed from __tests__/lib/download.test.ts)8
-rw-r--r--src/lib/images.ts (renamed from src/lib/download.ts)46
-rw-r--r--src/state/lib/api.ts3
-rw-r--r--src/view/com/composer/PhotoCarouselPicker.tsx54
-rw-r--r--src/view/com/modals/EditProfile.tsx23
-rw-r--r--src/view/com/util/UserAvatar.tsx4
-rw-r--r--src/view/com/util/UserBanner.tsx4
7 files changed, 101 insertions, 41 deletions
diff --git a/__tests__/lib/download.test.ts b/__tests__/lib/images.test.ts
index d90e8c895..461bd04cc 100644
--- a/__tests__/lib/download.test.ts
+++ b/__tests__/lib/images.test.ts
@@ -1,4 +1,4 @@
-import {downloadAndResize, DownloadAndResizeOpts} from '../../src/lib/download'
+import {downloadAndResize, DownloadAndResizeOpts} from '../../src/lib/images'
 import ImageResizer from '@bam.tech/react-native-image-resizer'
 import RNFetchBlob from 'rn-fetch-blob'
 
@@ -16,6 +16,7 @@ describe('downloadAndResize', () => {
 
   const mockResizedImage = {
     path: jest.fn().mockReturnValue('file://resized-image.jpg'),
+    size: 100,
   }
 
   beforeEach(() => {
@@ -37,6 +38,7 @@ describe('downloadAndResize', () => {
       uri: 'https://example.com/image.jpg',
       width: 100,
       height: 100,
+      maxSize: 500000,
       mode: 'cover',
       timeout: 10000,
     }
@@ -56,7 +58,7 @@ describe('downloadAndResize', () => {
       100,
       100,
       'JPEG',
-      0.7,
+      1,
       undefined,
       undefined,
       undefined,
@@ -69,6 +71,7 @@ describe('downloadAndResize', () => {
       uri: 'invalid-uri',
       width: 100,
       height: 100,
+      maxSize: 500000,
       mode: 'cover',
       timeout: 10000,
     }
@@ -83,6 +86,7 @@ describe('downloadAndResize', () => {
       uri: 'https://example.com/image.bmp',
       width: 100,
       height: 100,
+      maxSize: 500000,
       mode: 'cover',
       timeout: 10000,
     }
diff --git a/src/lib/download.ts b/src/lib/images.ts
index c53d809b1..caeb96d27 100644
--- a/src/lib/download.ts
+++ b/src/lib/images.ts
@@ -1,11 +1,13 @@
 import RNFetchBlob from 'rn-fetch-blob'
 import ImageResizer from '@bam.tech/react-native-image-resizer'
+import {Image as PickedImage} from 'react-native-image-crop-picker'
 
 export interface DownloadAndResizeOpts {
   uri: string
   width: number
   height: number
   mode: 'contain' | 'cover' | 'stretch'
+  maxSize: number
   timeout: number
 }
 
@@ -41,21 +43,55 @@ export async function downloadAndResize(opts: DownloadAndResizeOpts) {
       localUri = `file://${localUri}`
     }
 
+    return await resize(localUri, opts)
+  } finally {
+    if (downloadRes) {
+      downloadRes.flush()
+    }
+  }
+}
+
+export interface ResizeOpts {
+  width: number
+  height: number
+  mode: 'contain' | 'cover' | 'stretch'
+  maxSize: number
+}
+
+export async function resize(localUri: string, opts: ResizeOpts) {
+  for (let i = 0; i < 9; i++) {
+    const quality = 1.0 - i / 10
     const resizeRes = await ImageResizer.createResizedImage(
       localUri,
       opts.width,
       opts.height,
       'JPEG',
-      0.7,
+      quality,
       undefined,
       undefined,
       undefined,
       {mode: opts.mode},
     )
-    return resizeRes
-  } finally {
-    if (downloadRes) {
-      downloadRes.flush()
+    console.log(quality, resizeRes)
+    if (resizeRes.size < opts.maxSize) {
+      return resizeRes
     }
   }
+  throw new Error(
+    `This image is too big! We couldn't compress it down to ${opts.maxSize} bytes`,
+  )
+}
+
+export async function compressIfNeeded(img: PickedImage, maxSize: number) {
+  const origUri = `file://${img.path}`
+  if (img.size < maxSize) {
+    return origUri
+  }
+  const resizeRez = await resize(origUri, {
+    width: img.width,
+    height: img.height,
+    mode: 'stretch',
+    maxSize,
+  })
+  return resizeRez.uri
 }
diff --git a/src/state/lib/api.ts b/src/state/lib/api.ts
index f0f33d134..31df20468 100644
--- a/src/state/lib/api.ts
+++ b/src/state/lib/api.ts
@@ -14,7 +14,7 @@ import {AtUri} from '../../third-party/uri'
 import {RootStoreModel} from '../models/root-store'
 import {extractEntities} from '../../lib/strings'
 import {isNetworkError} from '../../lib/errors'
-import {downloadAndResize} from '../../lib/download'
+import {downloadAndResize} from '../../lib/images'
 import {getLikelyType, LikelyType, getLinkMeta} from '../../lib/link-meta'
 
 const TIMEOUT = 10e3 // 10s
@@ -85,6 +85,7 @@ export async function post(
             width: 250,
             height: 250,
             mode: 'contain',
+            maxSize: 100000,
             timeout: 15e3,
           }).catch(() => undefined)
           if (thumbLocal) {
diff --git a/src/view/com/composer/PhotoCarouselPicker.tsx b/src/view/com/composer/PhotoCarouselPicker.tsx
index 86d23313d..2afe2e5a5 100644
--- a/src/view/com/composer/PhotoCarouselPicker.tsx
+++ b/src/view/com/composer/PhotoCarouselPicker.tsx
@@ -7,13 +7,14 @@ import {
   openCamera,
   openCropper,
 } from 'react-native-image-crop-picker'
+import {compressIfNeeded} from '../../../lib/images'
 
 const IMAGE_PARAMS = {
   width: 500,
   height: 500,
   freeStyleCropEnabled: true,
   forceJpg: true, // ios only
-  compressImageQuality: 0.7,
+  compressImageQuality: 1.0,
 }
 
 export const PhotoCarouselPicker = ({
@@ -25,29 +26,35 @@ export const PhotoCarouselPicker = ({
   onSelectPhotos: (v: string[]) => void
   localPhotos: any
 }) => {
-  const handleOpenCamera = useCallback(() => {
-    openCamera({
-      mediaType: 'photo',
-      cropping: true,
-      ...IMAGE_PARAMS,
-    }).then(
-      item => {
-        onSelectPhotos([item.path, ...selectedPhotos])
-      },
-      _err => {
-        // ignore
-      },
-    )
+  const handleOpenCamera = useCallback(async () => {
+    try {
+      const cameraRes = await openCamera({
+        mediaType: 'photo',
+        cropping: true,
+        ...IMAGE_PARAMS,
+      })
+      const uri = await compressIfNeeded(cameraRes, 300000)
+      onSelectPhotos([uri, ...selectedPhotos])
+    } catch (err) {
+      // ignore
+      console.log('Error using camera', err)
+    }
   }, [selectedPhotos, onSelectPhotos])
 
   const handleSelectPhoto = useCallback(
     async (uri: string) => {
-      const img = await openCropper({
-        mediaType: 'photo',
-        path: uri,
-        ...IMAGE_PARAMS,
-      })
-      onSelectPhotos([img.path, ...selectedPhotos])
+      try {
+        const cropperRes = await openCropper({
+          mediaType: 'photo',
+          path: uri,
+          ...IMAGE_PARAMS,
+        })
+        const finalUri = await compressIfNeeded(cropperRes, 300000)
+        onSelectPhotos([finalUri, ...selectedPhotos])
+      } catch (err) {
+        // ignore
+        console.log('Error selecting photo', err)
+      }
     },
     [selectedPhotos, onSelectPhotos],
   )
@@ -60,13 +67,14 @@ export const PhotoCarouselPicker = ({
     }).then(async items => {
       const result = []
 
-      for await (const image of items) {
-        const img = await openCropper({
+      for (const image of items) {
+        const cropperRes = await openCropper({
           mediaType: 'photo',
           path: image.path,
           ...IMAGE_PARAMS,
         })
-        result.push(img.path)
+        const finalUri = await compressIfNeeded(cropperRes, 300000)
+        result.push(finalUri)
       }
       onSelectPhotos([...result, ...selectedPhotos])
     })
diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx
index 1ebd4e767..bd97ced53 100644
--- a/src/view/com/modals/EditProfile.tsx
+++ b/src/view/com/modals/EditProfile.tsx
@@ -20,6 +20,7 @@ import {
   MAX_DESCRIPTION,
 } from '../../../lib/strings'
 import {isNetworkError} from '../../../lib/errors'
+import {compressIfNeeded} from '../../../lib/images'
 import {UserBanner} from '../util/UserBanner'
 import {UserAvatar} from '../util/UserAvatar'
 
@@ -52,13 +53,23 @@ export function Component({
   const onPressCancel = () => {
     store.shell.closeModal()
   }
-  const onSelectNewAvatar = (img: PickedImage) => {
-    setNewUserAvatar(img)
-    setUserAvatar(img.path)
+  const onSelectNewAvatar = async (img: PickedImage) => {
+    try {
+      setNewUserAvatar(img)
+      const uri = await compressIfNeeded(img, 300000)
+      setUserAvatar(uri)
+    } catch (e: any) {
+      setError(e.message || e.toString())
+    }
   }
-  const onSelectNewBanner = (img: PickedImage) => {
-    setNewUserBanner(img)
-    setUserBanner(img.path)
+  const onSelectNewBanner = async (img: PickedImage) => {
+    try {
+      setNewUserBanner(img)
+      const uri = await compressIfNeeded(img, 500000)
+      setUserBanner(uri)
+    } catch (e: any) {
+      setError(e.message || e.toString())
+    }
   }
   const onPressSave = async () => {
     setProcessing(true)
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx
index 07d657007..67f039551 100644
--- a/src/view/com/util/UserAvatar.tsx
+++ b/src/view/com/util/UserAvatar.tsx
@@ -39,7 +39,7 @@ export function UserAvatar({
             height: 400,
             cropperCircleOverlay: true,
             forceJpg: true, // ios only
-            compressImageQuality: 0.7,
+            compressImageQuality: 1,
           }).then(onSelectNewAvatar)
         },
       },
@@ -56,7 +56,7 @@ export function UserAvatar({
               height: 400,
               cropperCircleOverlay: true,
               forceJpg: true, // ios only
-              compressImageQuality: 0.7,
+              compressImageQuality: 1,
             }).then(onSelectNewAvatar)
           })
         },
diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx
index 697e900b1..5137e19ca 100644
--- a/src/view/com/util/UserBanner.tsx
+++ b/src/view/com/util/UserBanner.tsx
@@ -35,7 +35,7 @@ export function UserBanner({
             compressImageMaxHeight: 500,
             height: 500,
             forceJpg: true, // ios only
-            compressImageQuality: 0.4,
+            compressImageQuality: 1,
             includeExif: true,
           }).then(onSelectNewBanner)
         },
@@ -54,7 +54,7 @@ export function UserBanner({
               compressImageMaxHeight: 500,
               height: 500,
               forceJpg: true, // ios only
-              compressImageQuality: 0.4,
+              compressImageQuality: 1,
               includeExif: true,
             }).then(onSelectNewBanner)
           })