Впровадження редагування зображень з штучним інтелектом (Outpainting) в мобільному додатку
Outpainting розширює зображення за його межами. Користувач завантажує фото 9:16, хоче отримати 16:9 — модель дорисовує бічні частини, зберігаючи стиль і контекст. Або хоче «відсунути камеру»: показати більше оточення навколо об'єкта. DALL-E 2 підтримує це нативно, Stable Diffusion — через те ж inpainting з розширеним полотном.
Логіка на клієнті: підготовка полотна
Суть outpainting: розміс оригіналу на більше полотно (з прозорими або нейтральними полями), відправ на API як image + mask (де mask = нові пусті області), отримай заповнений результат.
func prepareOutpaintingCanvas(
original: UIImage,
targetSize: CGSize,
placement: CGPoint // де розмістити оригінал на полотні
) -> (image: UIImage, mask: UIImage) {
// Створюємо полотно потрібного розміру
UIGraphicsBeginImageContextWithOptions(targetSize, false, 1.0)
let context = UIGraphicsGetCurrentContext()!
// Заливаємо сірим (нейтральний колір для областей розширення)
context.setFillColor(UIColor.gray.cgColor)
context.fill(CGRect(origin: .zero, size: targetSize))
// Вставляємо оригінал
original.draw(at: placement)
let compositeImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
// Створюємо маску: чорні пікселі = зберігти, білі = дорисувати
// Для DALL-E: альфа-канал замість ч/б
UIGraphicsBeginImageContextWithOptions(targetSize, false, 1.0)
let maskContext = UIGraphicsGetCurrentContext()!
// Білі (прозорі для DALL-E) — нові області
maskContext.setFillColor(UIColor.white.cgColor)
maskContext.fill(CGRect(origin: .zero, size: targetSize))
// Чорні (непрозорі) — оригінальне зображення
maskContext.setFillColor(UIColor.black.cgColor)
maskContext.fill(CGRect(origin: placement, size: original.size))
let maskImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return (compositeImage, maskImage)
}
Позиція оригіналу на полотні визначає напрямок розширення:
- Центр полотна → розширення з усіх сторін
- Лівий край → розширення тільки вправо
- Довільно → користувач керує через drag
UI керування розширенням
Паттерн взаємодії:
- Користувач бачить вихідне зображення у «рамці» полотна
- Може перетягувати зображення всередину рамки (або рухати рамку)
- Вибирає остаточний aspect ratio: 16:9, 4:3, 1:1, або довільний
- Натискає «Розширити»
// Android: draggable image inside canvas with GestureDetector
class OutpaintingView(context: Context) : View(context) {
var imageOffsetX = 0f
var imageOffsetY = 0f
private val gestureDetector = GestureDetector(context, object : SimpleOnGestureListener() {
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dx: Float, dy: Float): Boolean {
imageOffsetX -= dx
imageOffsetY -= dy
// Обмежуємо смищення межами полотна
imageOffsetX = imageOffsetX.coerceIn(-maxOffsetX, 0f)
imageOffsetY = imageOffsetY.coerceIn(-maxOffsetY, 0f)
invalidate()
return true
}
})
override fun onDraw(canvas: Canvas) {
canvas.drawColor(Color.DKGRAY) // фон розширюємої області
canvas.drawBitmap(originalBitmap, imageOffsetX, imageOffsetY, null)
}
}
Відправка на DALL-E 2
// DALL-E 2: image та mask передаються як PNG з альфа-каналом
// У маці: прозорість = редагувати, непрозорість = зберігти
func outpaint(composite: UIImage, mask: UIImage, prompt: String, targetSize: String = "1024x1024") async throws -> UIImage {
// Конвертуємо маску: білі пікселі → прозорі (альфа = 0)
let alphaMask = convertToAlphaMask(mask)
guard let compositeData = composite.pngData(),
let maskData = alphaMask.pngData() else { throw OutpaintError.conversionFailed }
// Запрос ідентичний inpainting — той ж ендпоінт /v1/images/edits
return try await sendInpaintRequest(imageData: compositeData, maskData: maskData, prompt: prompt, size: targetSize)
}
private func convertToAlphaMask(_ mask: UIImage) -> UIImage {
UIGraphicsBeginImageContextWithOptions(mask.size, false, 1.0)
guard let context = UIGraphicsGetCurrentContext() else { return mask }
// Інвертуємо: біле → прозоре
context.setBlendMode(.normal)
mask.draw(in: CGRect(origin: .zero, size: mask.size))
// Інверсія альфа-каналу через CIImage
UIGraphicsEndImageContext()
// Використовуємо Core Image для інверсії маски
let ciImage = CIImage(image: mask)!
let inverted = ciImage.applyingFilter("CIColorInvert")
return UIImage(ciImage: inverted)
}
Обмеження розмірів
DALL-E 2 приймає лише квадратні зображення: 256×256, 512×512, 1024×1024. Для outpainting у 16:9 потрібне проміжне рішення: генеруємо в 1024×1024, потім кропаємо до потрібного ratio. Або робимо кілька ітерацій outpainting.
Stable Diffusion через Replicate/FAL приймає довільні розміри (кратні 64), що зручніше для outpainting у нестандартних ratio.
Типові проблеми
Видимий шов на границі оригіналу та дорисованої частини — трапляється коли оригінал розміщений із гострим краєм на нейтральному фоні. Рішення: невеликий градієнтний перехід (feather) на краях маски — розмиваємо маску на 10–20 пікселів.
Невідповідність стилю — модель дорисувала щось зовсім іншого стилю. Промпт повинен описувати всю сцену, не лише нову частину: «сонячний міський пейзаж, фотореалізм, теплі тони» а не «продовж».
Терміни
Базовий outpainting з DALL-E 2 (фіксовані напрямки розширення) — 5–7 днів. Інтерактивне draggable полотно з довільним позиціонуванням, множественні ітерації, історія — 3–4 тижні.







