Increase image upload resolution

This commit is contained in:
Eric Bailey
2026-03-18 18:01:53 -05:00
parent dc8570c524
commit 70a430ceb6
6 changed files with 61 additions and 10 deletions
+1
View File
@@ -9,6 +9,7 @@ export enum Features {
ImportContactsOnboardingDisable = 'import_contacts:onboarding:disable', ImportContactsOnboardingDisable = 'import_contacts:onboarding:disable',
ImportContactsSettingsDisable = 'import_contacts:settings:disable', ImportContactsSettingsDisable = 'import_contacts:settings:disable',
LiveNowBetaDisable = 'live_now_beta:disable', LiveNowBetaDisable = 'live_now_beta:disable',
ImageUploadsHighResolution = 'image_uploads:high_resolution',
AATest = 'aa-test', AATest = 'aa-test',
} }
+3
View File
@@ -230,6 +230,9 @@ export type Events = {
'composer:gif:open': {} 'composer:gif:open': {}
'composer:gif:select': {} 'composer:gif:select': {}
'composer:image:edit': {
platform: Platform['OS']
}
'composerPrompt:press': {} 'composerPrompt:press': {}
'composerPrompt:camera:press': {} 'composerPrompt:camera:press': {}
'composerPrompt:gallery:press': {} 'composerPrompt:gallery:press': {}
+19 -2
View File
@@ -51,10 +51,15 @@ interface PostOpts {
langs?: string[] langs?: string[]
} }
type FeatureFlags = {
highResolutionImages?: boolean
}
export async function post( export async function post(
agent: BskyAgent, agent: BskyAgent,
queryClient: QueryClient, queryClient: QueryClient,
opts: PostOpts, opts: PostOpts,
featureFlags?: FeatureFlags,
) { ) {
const thread = opts.thread const thread = opts.thread
opts.onStateChange?.(t`Processing...`) opts.onStateChange?.(t`Processing...`)
@@ -91,6 +96,7 @@ export async function post(
queryClient, queryClient,
draft, draft,
opts.onStateChange, opts.onStateChange,
featureFlags,
) )
let labels: $Typed<ComAtprotoLabelDefs.SelfLabels> | undefined let labels: $Typed<ComAtprotoLabelDefs.SelfLabels> | undefined
if (draft.labels.length) { if (draft.labels.length) {
@@ -230,6 +236,7 @@ async function resolveEmbed(
queryClient: QueryClient, queryClient: QueryClient,
draft: PostDraft, draft: PostDraft,
onStateChange: ((state: string) => void) | undefined, onStateChange: ((state: string) => void) | undefined,
featureFlags?: FeatureFlags,
): Promise< ): Promise<
| $Typed<AppBskyEmbedImages.Main> | $Typed<AppBskyEmbedImages.Main>
| $Typed<AppBskyEmbedVideo.Main> | $Typed<AppBskyEmbedVideo.Main>
@@ -240,7 +247,13 @@ async function resolveEmbed(
> { > {
if (draft.embed.quote) { if (draft.embed.quote) {
const [resolvedMedia, resolvedQuote] = await Promise.all([ const [resolvedMedia, resolvedQuote] = await Promise.all([
resolveMedia(agent, queryClient, draft.embed, onStateChange), resolveMedia(
agent,
queryClient,
draft.embed,
onStateChange,
featureFlags,
),
resolveRecord(agent, queryClient, draft.embed.quote.uri), resolveRecord(agent, queryClient, draft.embed.quote.uri),
]) ])
if (resolvedMedia) { if (resolvedMedia) {
@@ -263,6 +276,7 @@ async function resolveEmbed(
queryClient, queryClient,
draft.embed, draft.embed,
onStateChange, onStateChange,
featureFlags,
) )
if (resolvedMedia) { if (resolvedMedia) {
return resolvedMedia return resolvedMedia
@@ -288,6 +302,7 @@ async function resolveMedia(
queryClient: QueryClient, queryClient: QueryClient,
embedDraft: EmbedDraft, embedDraft: EmbedDraft,
onStateChange: ((state: string) => void) | undefined, onStateChange: ((state: string) => void) | undefined,
featureFlags?: FeatureFlags,
): Promise< ): Promise<
| $Typed<AppBskyEmbedExternal.Main> | $Typed<AppBskyEmbedExternal.Main>
| $Typed<AppBskyEmbedImages.Main> | $Typed<AppBskyEmbedImages.Main>
@@ -303,7 +318,9 @@ async function resolveMedia(
const images: AppBskyEmbedImages.Image[] = await Promise.all( const images: AppBskyEmbedImages.Image[] = await Promise.all(
imagesDraft.map(async (image, i) => { imagesDraft.map(async (image, i) => {
logger.debug(`Compressing image #${i}`) logger.debug(`Compressing image #${i}`)
const {path, width, height, mime} = await compressImage(image) const {path, width, height, mime} = await compressImage(image, {
highResolution: featureFlags?.highResolutionImages,
})
logger.debug(`Uploading image #${i}`) logger.debug(`Uploading image #${i}`)
const res = await uploadBlob(agent, path, mime) const res = await uploadBlob(agent, path, mime)
return { return {
+16 -2
View File
@@ -200,10 +200,24 @@ export function resetImageManipulation(
return img return img
} }
export async function compressImage(img: ComposerImage): Promise<PickerImage> { export async function compressImage(
img: ComposerImage,
options?: {
highResolution?: boolean
},
): Promise<PickerImage> {
const source = img.transformed || img.source const source = img.transformed || img.source
const [w, h] = containImageRes(source.width, source.height, POST_IMG_MAX) const [w, h] = containImageRes(
source.width,
source.height,
options?.highResolution
? {
width: 4000,
height: 4000,
}
: POST_IMG_MAX,
)
let minQualityPercentage = 0 let minQualityPercentage = 0
let maxQualityPercentage = 101 // exclusive let maxQualityPercentage = 101 // exclusive
+15 -6
View File
@@ -825,12 +825,21 @@ export const ComposePost = ({
try { try {
logger.info(`composer: posting...`) logger.info(`composer: posting...`)
postUri = ( postUri = (
await apilib.post(agent, queryClient, { await apilib.post(
thread, agent,
replyTo: replyTo?.uri, queryClient,
onStateChange: setPublishingStage, {
langs: currentLanguages, thread,
}) replyTo: replyTo?.uri,
onStateChange: setPublishingStage,
langs: currentLanguages,
},
{
highResolutionImages: ax.features.enabled(
ax.features.ImageUploadsHighResolution,
),
},
)
).uris[0] ).uris[0]
/* /*
+7
View File
@@ -4,6 +4,7 @@ import {
type ImageStyle, type ImageStyle,
Keyboard, Keyboard,
type LayoutChangeEvent, type LayoutChangeEvent,
Platform,
StyleSheet, StyleSheet,
TouchableOpacity, TouchableOpacity,
View, View,
@@ -24,6 +25,7 @@ import {Admonition} from '#/components/Admonition'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_IOS, IS_NATIVE} from '#/env' import {IS_IOS, IS_NATIVE} from '#/env'
import {type PostAction} from '../state/composer' import {type PostAction} from '../state/composer'
import {EditImageDialog} from './EditImageDialog' import {EditImageDialog} from './EditImageDialog'
@@ -147,6 +149,7 @@ const GalleryItem = ({
}: GalleryItemProps): React.ReactNode => { }: GalleryItemProps): React.ReactNode => {
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const altTextControl = Dialog.useDialogControl() const altTextControl = Dialog.useDialogControl()
const editControl = Dialog.useDialogControl() const editControl = Dialog.useDialogControl()
@@ -161,6 +164,10 @@ const GalleryItem = ({
} }
const onImageEdit = () => { const onImageEdit = () => {
ax.metric('composer:image:edit', {
platform: Platform.OS,
})
if (IS_NATIVE) { if (IS_NATIVE) {
cropImage(image).then(next => { cropImage(image).then(next => {
onChange(next) onChange(next)