Skip to content

Conversation

@erikackermann
Copy link

@erikackermann erikackermann commented Nov 6, 2025

First time contributor checklist

Contributor checklist

  • My commits are rebased on the latest main branch
  • My commits are in nice logical chunks
  • My contribution is fully baked and is ready to be merged as is
  • I have tested my contribution on these devices:
  • iPhone 17 Pro Simulator, iOS 26.0.1
  • iPad Pro 13-inch (M4) Simulator, iPad OS 26.0.1
  • iPhone 17 Pro, iOS 26.1.0

Tested sending and receiving from the above devices, plus the following flows:

  • sending to multiple recipients (with varying toggle states)
  • copying images into the chat, sending with full metadata
  • sending from the share extension
  • sending from the Home screen of the app (CameraFirstCaptureSendFlow)

Description

This PR adds an opt-in, per-thread setting that allows users to send images in their original format with full metadata (EXIF, GPS, etc.) preserved. When enabled for a conversation, images are sent as original files (HEIC, PNG, JPEG) up to 100 MiB without compression or metadata stripping.

User-Facing Changes

Thread Settings

Users can now enable "Original Quality" for individual conversations through a new Image Quality settings screen accessible from the conversation settings menu.

Image Quality Settings Screen

The setting is a simple toggle:

  • Default: Uses system-standard quality with metadata removed
  • Original Quality: Sends images in original format with all metadata preserved

A warning message informs users that Original Quality preserves location data and other metadata that could inadvertently reveal where photos were taken.

Attachment Approval Screen

When sending images, users see a quality selector that respects the per-thread setting:

Attachment Approval Quality Selector
  • For conversations with Original Quality enabled, the selector defaults to "Original"
  • In multi-recipient sends, Original Quality applies only to threads where it's enabled
  • The UI shows "Standard" and "Original" options (or "Standard" and "High" when Original isn't available)
  • Accessible via VoiceOver with adjustable trait support

Technical Implementation

Storage Layer

  • ImageQualitySettingStore: Per-thread key-value storage for quality preferences (default vs original)
  • ImageQualityLevel.original: New enum case with 100 MiB max file size

Attachment Pipeline

  • SignalAttachment.preserveMetadata: Flag to control metadata stripping
  • preparedForOutput(qualityLevel:): Defers image conversion; returns original files when quality is .original
  • PhotoLibrary: Always exports original file bytes via PHAssetResourceManager; metadata stripping happens in preparedForOutput() based on quality level

UI Integration

  • ImageQualitySettingsViewController: New per-thread settings screen with toggle switch
  • AttachmentApprovalViewController: 2-button quality selector with per-thread default
  • MediaItemViewController: Fixed preview to use attachmentStream.decryptedImage() so "Save from preview" preserves EXIF metadata

Multi-Recipient Support

  • AttachmentMultisend: Processes attachments once at the highest quality needed across all recipients
  • Threads with Original enabled receive original files; others receive high/standard quality

Testing Notes

  • Original Quality setting persists per-thread
  • Images sent with Original Quality retain EXIF/GPS metadata
  • Preview → Share → Save preserves metadata
  • Multi-recipient sends respect individual thread settings
  • Standard quality still strips metadata as before
  • File size limit enforced at 100 MiB for original quality

Migration

No database migration required. The setting is additive and defaults to system behavior for all existing conversations.

- Add ImageQualitySettingStore for per-thread quality preferences
  - Simple toggle: default vs original
  - Stores per-thread setting in KeyValueStore
  - resolvedQualityLevel() falls back to system default
- Add ImageQualityLevel.original enum case
  - Max file size: 100 MiB (vs 6 MiB for high quality)
  - Starting tier: .seven (highest quality)
  - Localized as 'Original'

This provides the foundation for opt-in per-thread original quality images
with full metadata preservation.
SignalAttachment changes:
- Add preserveMetadata flag to control metadata stripping
- Defer image conversion to preparedForOutput(qualityLevel:)
- Early-return original files when qualityLevel is .original
- Implement removingImageMetadata() for when conversion is needed
- Update isValidOutputOriginal to accept input image formats

PhotoLibrary changes:
- Always use PHAssetResourceManager to get original file bytes
- Preserves all metadata (EXIF, GPS, etc.) in exported data
- Metadata stripping now happens in preparedForOutput() based on quality

This allows sending original HEIC/PNG/JPEG files with full metadata when
Original quality is selected, while still supporting metadata removal for
other quality levels.
- Add ImageQualitySettingsViewController
  - Simple toggle: Default vs Original Quality
  - Warning text about metadata/location preservation
  - Push navigation from conversation settings
- Integrate into ConversationSettingsViewController
  - New 'Image Quality' row shows current setting
  - Displays 'Default' or 'Original Quality'
- Add localization strings for settings and quality options
- Update Xcode project to include new view controller

Users can now opt-in to Original quality per conversation, which will
preserve all image metadata including GPS location data.
AttachmentApprovalViewController:
- Add 2-button quality selector (Standard | Original)
- Read per-thread setting and default to Original when enabled
- Show Original option in multi-recipient flows with subtitle 'Applies where enabled'
- Set preserveMetadata flag when Original is selected
- Accessibility support for quality control (VoiceOver adjustable)

MediaItemViewController:
- Fix preview to use attachmentStream.decryptedImage()
- Ensures 'Save from preview' preserves EXIF metadata

Flow integration:
- Pass thread context to AttachmentApprovalViewController where available
- Update SendMediaNavigationController, ConversationViewController,
  CameraFirstCaptureSendFlow, and SharingThreadPickerViewController

The approval UI now respects per-thread Original quality settings and
defaults the quality selector appropriately.
- Implement per-thread quality processing in AttachmentMultisend
- Read each destination thread's quality setting
- Process attachments once at the highest quality level needed
- Original quality recipients get original files with metadata
- Other recipients get downsampled versions (high/standard)

This respects per-thread Original quality settings even when sending to
multiple recipients with different quality preferences, while minimizing
redundant processing by preparing attachments at the max quality needed.
@erikackermann erikackermann force-pushed the feature/send-original-quality-images branch from 547cbd9 to 33970ac Compare November 7, 2025 15:56
@sashaweiss-signal
Copy link
Contributor

Hi, thanks for your interest in contributing. This is something we've considered in the past, and there are things we want to improve in this space, but unfortunately I don't think this is the direction we'd take. (Due to the surface area and product/UX implications, this type of complex feature is also generally difficult for us to integrate from a public contribution.)

Metadata preservation is something we intentionally don't support, and we believe it's important that that be a uniform decision.

With regards to image quality, we already expose a "Sent Media Quality" setting in Settings > Data Usage and on a per-send basis. We understand that regardless of the Sent Media Quality setting, media sending over Signal on iOS often has subpar performance. Fortunately, we have significant improvements to media quality and performance under active development, as it's something we care a lot about and friction we ourselves experience all the time.

Thanks again for your interest in contributing, and as always for being a Signal user.

@erikackermann
Copy link
Author

erikackermann commented Nov 13, 2025

Hey thanks for your reply. I understand, this is a large change with many UX implications 😄

Metadata preservation is something we intentionally don't support, and we believe it's important that that be a uniform decision.

Can you explain what you mean by uniform decision? This option could also be implemented similar to disappearing messages (shared setting for all users in a thread) or as a global per-sender setting, but both of these are less privacy-preserving options.

Photo metadata (plus full image quality) is nice for photo archiving and to utilize the full features of photo organization apps. And it is frustrating that when chatting with trusted contacts (close friends / family), metadata is always removed if sending with the nice photo picker UI. This can already be worked around by sending photos as attachments, but this UX is terrible; it would be nice if users could enable sending exact byte-for-byte images to select chats.

In any case, this PR demonstrates that this can be achieved with a simple and clean implementation. I hope you will reconsider this as part of your photo sharing improvements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants