<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Mobile App Development – IMG.LY Blog</title><description>Posts tagged Mobile App Development on the IMG.LY blog.</description><link>https://img.ly/blog/tag/mobile-app-development/</link><language>en-us</language><image><url>https://img.ly/apple-touch-icon.png</url><title>Mobile App Development – IMG.LY Blog</title><link>https://img.ly/blog/tag/mobile-app-development/</link></image><atom:link href="https://img.ly/blog/tag/mobile-app-development/rss.xml" rel="self" type="application/rss+xml"/><generator>Astro</generator><lastBuildDate>Tue, 09 Jun 2026 09:48:33 GMT</lastBuildDate><ttl>60</ttl><item><title>CE.SDK v1.70 Release Notes</title><link>https://img.ly/blog/creative-editor-sdk-v-1-70-0-release-notes/</link><guid isPermaLink="true">https://img.ly/blog/creative-editor-sdk-v-1-70-0-release-notes/</guid><description>A sharper Android timeline, video duration controls on mobile, and a color editing workflow that finally keeps up with modern design tools.</description><pubDate>Tue, 10 Mar 2026 16:08:17 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://img.ly/blog/creative-editor-sdk-v-1-69-0-release-notes/&quot;&gt;Fresh off v1.69&lt;/a&gt; (our biggest developer experience release yet), v1.70 now strengthens what’s already there. The Android video timeline gets a professional-grade overhaul, iOS and Android gain video duration controls. Under the hood, significant stability improvements across every platform.&lt;/p&gt;
&lt;p&gt;Let’s dive in!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;edit-videos-with-clarity-on-android&quot;&gt;Edit Videos with Clarity on Android&lt;/h2&gt;
&lt;p&gt;These updates to the Android video timeline improve how editing looks and feels for your mobile users.&lt;/p&gt;
&lt;h3 id=&quot;use-real-audio-waveforms-for-accurate-video-editing&quot;&gt;Use Real Audio Waveforms for Accurate Video Editing&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1480px) 1480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1480&quot; height=&quot;643&quot; src=&quot;https://img.ly/_astro/waveform_1nnKQN.webp&quot; srcset=&quot;/_astro/waveform_sjaFV.webp 640w, /_astro/waveform_Z1C0gmM.webp 750w, /_astro/waveform_Z1v7ohn.webp 828w, /_astro/waveform_ZNCNJT.webp 1080w, /_astro/waveform_hmXxW.webp 1280w, /_astro/waveform_1nnKQN.webp 1480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Audio clips now display actual waveform visualization, replacing the previous placeholder. Users can see exactly where audio peaks and drops — making precise edits faster and more intuitive.&lt;/p&gt;
&lt;p&gt;→ &lt;a href=&quot;https://img.ly/docs/cesdk/android/create-video/timeline-editor-912252/#audio-waveforms&quot;&gt;Explore Audio Waveform Docs&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;keep-your-timeline-clutter-free&quot;&gt;Keep Your Timeline Clutter-Free&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1480px) 1480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1480&quot; height=&quot;643&quot; src=&quot;https://img.ly/_astro/timeline-android_Zukrgr.webp&quot; srcset=&quot;/_astro/timeline-android_2sonoo.webp 640w, /_astro/timeline-android_3uqC8.webp 750w, /_astro/timeline-android_Z2h6iMm.webp 828w, /_astro/timeline-android_r4uHi.webp 1080w, /_astro/timeline-android_Z1CsLz.webp 1280w, /_astro/timeline-android_Zukrgr.webp 1480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Images, stickers, and shapes now display &lt;em&gt;one&lt;/em&gt; thumbnail per clip, instead of repeating across the track. The timeline is cleaner and easier to read, keeping complex video compositions tidy on mobile screens.&lt;/p&gt;
&lt;h3 id=&quot;keep-text-visible-throughout-your-timeline&quot;&gt;Keep Text Visible Throughout Your Timeline&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;Keep your text clips apart: full text now visible in text clips.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1480px) 1480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1480&quot; height=&quot;643&quot; src=&quot;https://img.ly/_astro/timeline-android-ii_Z1Ofpkc.webp&quot; srcset=&quot;/_astro/timeline-android-ii_Z1atdup.webp 640w, /_astro/timeline-android-ii_1KhoOm.webp 750w, /_astro/timeline-android-ii_Z17x5Ka.webp 828w, /_astro/timeline-android-ii_uqgs3.webp 1080w, /_astro/timeline-android-ii_ZEp4qz.webp 1280w, /_astro/timeline-android-ii_Z1Ofpkc.webp 1480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Text clips now show their actual text content alongside the clip label, so users can identify and navigate text layers without opening each one.&lt;/p&gt;
&lt;h3 id=&quot;improvements&quot;&gt;Improvements&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Set Video Duration Limits on iOS and Android&lt;/strong&gt;&lt;br&gt;
You can now &lt;strong&gt;define minimum and maximum video duration&lt;/strong&gt; constraints directly in CE.SDK on both iOS and Android.&lt;/p&gt;
&lt;p&gt;Timeline markers make the boundaries visible to users while they edit, and export validation ensures clips meet the defined limits before rendering.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;edit-colors-like-a-pro--in-one-panel&quot;&gt;Edit Colors like a Pro – in One Panel&lt;/h2&gt;
&lt;p&gt;&lt;video src=&quot;https://blog.img.ly/2026/03/CYMK-Color-picker-set.mp4&quot; controls autoplay muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;color panel for web&lt;/strong&gt; is now unified: preview, saved colors, and custom editing controls all live in one place. Users can pick, adjust, and reuse colors in a single continuous workflow, without a separate modal or extra clicks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CMYK Color Mode for Web&lt;/strong&gt;&lt;br&gt;
Additionally, the web editor supports restricting the color picker to a specific color mode: RGB, CMYK. When CMYK is enforced, elements using a different color mode show a clear convert option rather than broken inputs.&lt;br&gt;
New pages, asset library blocks, and fill type switches automatically convert colors to match the configured mode.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;full-changelog&quot;&gt;Full Changelog&lt;/h2&gt;
&lt;p&gt;All technical details, breaking changes, and migration notes are in the &lt;a href=&quot;https://img.ly/docs/cesdk/changelog/v1-70-0/&quot;&gt;v1.70.0 Changelog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thank you for building with IMG.LY.&lt;/em&gt;&lt;/p&gt;</content:encoded><dc:creator>Neslihan</dc:creator><media:content url="https://blog.img.ly/2026/03/creative-sdk-imgly-170-release-notes-android-ios-design-video-photo-editor-integrate.jpg" medium="image"/><category>Release Notes</category><category>Mobile App Development</category></item><item><title>Best Video SDKs for Mobile Applications: A Comprehensive Comparison for Developers</title><link>https://img.ly/blog/best-video-sdks-for-mobile-applications-a-comprehensive-comparison-for-developers/</link><guid isPermaLink="true">https://img.ly/blog/best-video-sdks-for-mobile-applications-a-comprehensive-comparison-for-developers/</guid><description>Choosing the right video SDK for your mobile app is crucial. In this guide, we compare the top options in 2025: IMG.LY, Banuba, Meishe, BytePlus, FFmpeg, and GStreamer, to highlight their strengths, limitations, and ideal use cases so you can confidently pick the best fit for your product.</description><pubDate>Tue, 02 Dec 2025 14:54:37 GMT</pubDate><content:encoded>&lt;p&gt;If you’re building a mobile app that needs video editing capabilities, you’re facing a tough decision: should you integrate a commercial SDK, work with open-source tools, or try to build something yourself?&lt;/p&gt;
&lt;p&gt;Video editing on mobile isn’t just hard, it’s complex in ways that surprise even experienced developers. You’re dealing with device fragmentation, battery constraints, memory management, and user expectations shaped by apps like TikTok and CapCut. And unlike web apps, mobile video editing requires native performance optimization to feel responsive.&lt;/p&gt;
&lt;p&gt;In this article, we’ll compare the most popular &lt;strong&gt;video SDK options for mobile applications&lt;/strong&gt;: &lt;strong&gt;IMG.LY, Banuba, Meishe, BytePlus, FFmpeg, and GStreamer&lt;/strong&gt; and show you how they stack up across features, platforms, integration complexity, and pricing. We’ll be honest about where each solution shines and where you’ll hit roadblocks.&lt;/p&gt;
&lt;p&gt;We’ll also dig into what it actually takes to build with each option: difficulty of integration, performance considerations, and what happens when you need to scale beyond a prototype.&lt;/p&gt;
&lt;h2 id=&quot;the-challenge-building-video-editing-into-mobile-apps&quot;&gt;The Challenge: Building Video Editing Into Mobile Apps&lt;/h2&gt;
&lt;p&gt;Before diving into specific SDKs, let’s talk about why video editing on mobile is uniquely challenging.&lt;/p&gt;
&lt;h3 id=&quot;processing-power-vs-battery-life&quot;&gt;Processing Power vs Battery Life&lt;/h3&gt;
&lt;p&gt;Video processing is resource-intensive. Every frame you manipulate — trimming, filtering, compositing, exporting — requires significant CPU and GPU work. On mobile devices, this creates a constant tension between delivering fast performance and not draining your user’s battery in minutes.&lt;br&gt;
Approximately 60% of battery usage in media apps comes from data transmission and processing. If you’re not careful with how you handle encoding, memory management, and background processes, your app will get uninstalled fast.&lt;/p&gt;
&lt;h3 id=&quot;device-fragmentation&quot;&gt;Device Fragmentation&lt;/h3&gt;
&lt;p&gt;Unlike web apps where you can control the environment, mobile apps run on thousands of device configurations. An iPhone 15 Pro handles 4K video editing effortlessly; a three-year-old Android phone might struggle with 1080p. You need to account for different chipsets, GPU capabilities, screen sizes, and OS versions — all while maintaining consistent UX.&lt;/p&gt;
&lt;h3 id=&quot;user-expectations-are-higher-than-ever&quot;&gt;User Expectations Are Higher Than Ever&lt;/h3&gt;
&lt;p&gt;Users expect mobile video editors to feel as responsive as native camera apps. They want real-time preview, smooth timeline scrubbing, instant filter application, and fast exports. Anything that feels sluggish or unpolished gets compared unfavorably to TikTok, Instagram, or CapCut — apps with massive engineering teams and years of optimization work.&lt;/p&gt;
&lt;p&gt;Building this level of polish from scratch is a multi-month (or multi-year) undertaking. That’s why most teams reach for an SDK.&lt;/p&gt;
&lt;h2 id=&quot;understanding-video-sdk-options&quot;&gt;Understanding Video SDK Options&lt;/h2&gt;
&lt;p&gt;The video SDK landscape breaks down into three broad categories:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Commercial SDKs&lt;/strong&gt; (IMG.LY Banuba, Meishe, BytePlus) provide pre-built editors with UI components, effects libraries, and dedicated support. They’re designed to integrate quickly but come with licensing costs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Open-source frameworks&lt;/strong&gt; (FFmpeg, GStreamer) give you powerful processing engines but no UI, no templates, and no hand-holding. You’ll need to build everything user-facing yourself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hybrid approaches&lt;/strong&gt; combine open-source processing with custom UI development, giving you flexibility at the cost of significant engineering time.&lt;/p&gt;
&lt;p&gt;When evaluating options, consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Do you need a user-facing editor or just backend processing?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How important is cross-platform consistency?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What’s your timeline and team size?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What happens when you need to scale or add new features?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now let’s break down each solution.&lt;/p&gt;
&lt;h2 id=&quot;1-imgly-cesdk-the-complete-solution&quot;&gt;1. IMG.LY CE.SDK (The Complete Solution)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;IMG.LY CreativeEditor SDK&lt;/strong&gt; is an enterprise-grade video editing solution built for teams that need production-ready editing features, cross-platform consistency, and automation — without spending months building and maintaining a custom solution.&lt;/p&gt;
&lt;p&gt;Unlike processing libraries (FFmpeg, GStreamer) or social-first SDKs (Banuba, BytePlus), IMG.LY provides both interactive editing and server-side automation in one platform. You can embed the SDK for user-facing editing and use the same engine for headless creative automation on your backend.&lt;/p&gt;
&lt;h3 id=&quot;core-capabilities&quot;&gt;Core Capabilities&lt;/h3&gt;
&lt;p&gt;IMG.LY delivers a complete video editing platform with timeline-based editing, design tools, and AI features:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Timeline-Based Editing:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Arrange videos on multi-track timelines&lt;/li&gt;
&lt;li&gt;Trim, crop, and adjust duration&lt;/li&gt;
&lt;li&gt;Merge videos and create collages&lt;/li&gt;
&lt;li&gt;Audio overlay and mixing&lt;/li&gt;
&lt;li&gt;Layer management and compositing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Creative Tools:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;60+ filters and effects&lt;/li&gt;
&lt;li&gt;Adjustments (brightness, contrast, saturation, exposure)&lt;/li&gt;
&lt;li&gt;Text design with custom fonts and styles&lt;/li&gt;
&lt;li&gt;Dynamic stickers and overlays&lt;/li&gt;
&lt;li&gt;Frames with multiple blend modes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Template System:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Design templates with text variables and placeholders&lt;/li&gt;
&lt;li&gt;Lockable elements for brand consistency&lt;/li&gt;
&lt;li&gt;Automated variation generation&lt;/li&gt;
&lt;li&gt;Server-side rendering for mass personalization&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;AI &amp;#x26; Automation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Native AI features: video generation, captions, voiceovers, thumbnails&lt;/li&gt;
&lt;li&gt;Model-agnostic plugin system (integrate any AI model or API)&lt;/li&gt;
&lt;li&gt;Background removal and style transfer&lt;/li&gt;
&lt;li&gt;Smart filters and enhancements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Customization Framework:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Headless API for completely custom interfaces&lt;/li&gt;
&lt;li&gt;Theming to match your brand&lt;/li&gt;
&lt;li&gt;Toolbar configuration and element positioning&lt;/li&gt;
&lt;li&gt;Multi-language localization&lt;/li&gt;
&lt;li&gt;External asset library integration (Getty Images, Soundstripe)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;platform-support&quot;&gt;Platform Support&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Web:&lt;/strong&gt; JavaScript, React, Angular, Vue.js, Svelte, Next.js&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mobile:&lt;/strong&gt; Android (Kotlin/Java), iOS (Swift), React Native, Flutter&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Desktop:&lt;/strong&gt; macOS, Mac Catalyst, Electron&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server:&lt;/strong&gt; Node.js for backend automation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;technical-architecture&quot;&gt;Technical Architecture&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Client-side encoding&lt;/strong&gt; means fast editing operations run directly on devices without requiring backend infrastructure. This cuts bandwidth, reduces upload times for users, and eliminates server costs for standard editing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Server-side rendering&lt;/strong&gt; enables creative automation at scale — design templates once, generate thousands of variations programmatically from any data source with 100% consistent rendering.&lt;/p&gt;
&lt;h3 id=&quot;ideal-use-cases&quot;&gt;Ideal Use Cases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Social media creation:&lt;/strong&gt; In-app video recording with dual-camera support, advanced audio overlays, and real-time effects&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Template-based workflows:&lt;/strong&gt; Automate video creation for marketing materials, product videos, social media ads&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enterprise automation:&lt;/strong&gt; Server-side rendering for high-volume ad generation, creative pipelines, mass personalization&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;E-commerce:&lt;/strong&gt; Product video creation, user-generated content, personalized demos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SaaS platforms:&lt;/strong&gt; In-app video editing for user-generated content, brand-controlled templates&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;real-customer-examples&quot;&gt;Real Customer Examples&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Social Media &amp;#x26; Content Creation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mobile apps use IMG.LY SDK for in-app video recording with dual-camera support, allowing creators to capture picture-in-picture content directly&lt;/li&gt;
&lt;li&gt;Advanced audio overlay features enable users to mix music tracks, voiceovers, and original audio in real-time&lt;/li&gt;
&lt;li&gt;Real-time effects and filters provide instant creative feedback during recording and editing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Template-Based Video Production:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Marketing teams design video templates once and generate thousands of personalized variations programmatically using server-side rendering&lt;/li&gt;
&lt;li&gt;E-commerce platforms automate product video creation by mapping product data to video templates, producing consistent branded content at scale&lt;/li&gt;
&lt;li&gt;Brand teams create locked templates with variable elements, ensuring on-brand output while allowing distributed teams to customize messaging&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Enterprise Creative Automation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ad platforms use CE.SDK’s Node.js capabilities for high-volume programmatic video generation, creating thousands of ad variations from single templates&lt;/li&gt;
&lt;li&gt;DAM systems integrate IMG.LY for in-workflow video editing, allowing teams to make quick adjustments without leaving their asset management platform&lt;/li&gt;
&lt;li&gt;Publishing platforms automate social media content by rendering video templates with dynamic data feeds, producing daily content without manual editing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;500+ million creations per month&lt;/strong&gt; powered by IMG.LY’s video editing and automation infrastructure across 600+ customers&lt;/p&gt;
&lt;h3 id=&quot;limitations&quot;&gt;Limitations&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Paid solution.&lt;/strong&gt; Unlike open-source frameworks, CE.SDK requires an enterprise license. This makes it less accessible for hobby projects or very early-stage prototypes, but the investment pays off in reduced development time, ongoing support, and scalability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Enterprise-focused.&lt;/strong&gt; The SDK is built for teams that need reliability, support, and scale. If you’re building a quick prototype or low-traffic app, open-source tools might be more cost-effective initially (though you’ll pay in engineering time).&lt;/p&gt;
&lt;h3 id=&quot;pricing--licensing&quot;&gt;Pricing / Licensing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Custom enterprise licensing&lt;/strong&gt; based on platform, features, and usage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dedicated support&lt;/strong&gt; included: onboarding, SLAs, regular updates&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration time:&lt;/strong&gt; Most teams have a working editor in hours, not weeks&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;who-its-for&quot;&gt;Who It’s For&lt;/h3&gt;
&lt;p&gt;Businesses that need a &lt;strong&gt;complete, production-ready video editing solution&lt;/strong&gt; with cross-platform support, automation capabilities, enterprise scalability, and ongoing support. Teams that want to ship faster and focus on their unique product features rather than rebuilding video editing infrastructure.&lt;/p&gt;
&lt;h3 id=&quot;why-imgly-sdk-stands-out&quot;&gt;Why IMG.LY SDK Stands Out&lt;/h3&gt;
&lt;p&gt;While other SDKs focus on either interactive editing (Banuba, BytePlus) or backend processing (FFmpeg, GStreamer), &lt;strong&gt;IMG.LY is the only solution that combines:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cross-platform video editing (Web, iOS, Android, React Native, Flutter, Node.js)&lt;/li&gt;
&lt;li&gt;Polished editor UI with complete customization options&lt;/li&gt;
&lt;li&gt;Template system with server-side automation&lt;/li&gt;
&lt;li&gt;AI-powered creative features&lt;/li&gt;
&lt;li&gt;Dual-purpose architecture (client-side editing + server-side rendering)&lt;/li&gt;
&lt;li&gt;Enterprise-grade scalability and dedicated support&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re evaluating other SDKs, ask yourself: &lt;strong&gt;how much engineering time will you spend building, maintaining, and scaling a custom solution?&lt;/strong&gt; Our SDK eliminates that overhead so your team can focus on what makes your product unique.&lt;/p&gt;
&lt;h2 id=&quot;2-meishe-sdk&quot;&gt;2. Meishe SDK&lt;/h2&gt;
&lt;p&gt;Meishe is a Chinese video editing SDK provider with a comprehensive suite covering video editing, beauty filters, short video creation, and audio processing. They’re particularly strong in the Asian market and have been adopted by major platforms like Bilibili, OPPO, Xiaomi, and Himalaya.&lt;/p&gt;
&lt;h3 id=&quot;core-capabilities-1&quot;&gt;Core Capabilities&lt;/h3&gt;
&lt;p&gt;Meishe offers film and television-level processing capabilities with a focus on high-quality output:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video Editing Features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real-time preview through LiveWindow (preview effects during editing without pre-processing)&lt;/li&gt;
&lt;li&gt;Support for multiple file formats with input up to 4K&lt;/li&gt;
&lt;li&gt;Video output up to 1080P with configurable quality levels (1080P, 720P, 480P)&lt;/li&gt;
&lt;li&gt;Unlimited audio track editing with multi-segment support&lt;/li&gt;
&lt;li&gt;Transition effects between clips with customization options&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Creative Tools:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real-time skin beautification with adjustable parameters&lt;/li&gt;
&lt;li&gt;Animation stickers with customizable properties&lt;/li&gt;
&lt;li&gt;Special effects filters&lt;/li&gt;
&lt;li&gt;Full-link HDR support&lt;/li&gt;
&lt;li&gt;Professional video/audio editing with free function combination&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Technical Details:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4K shooting capability&lt;/li&gt;
&lt;li&gt;Real-time special effects processing&lt;/li&gt;
&lt;li&gt;Full-link HDR processing&lt;/li&gt;
&lt;li&gt;Professional-grade video and audio editing&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;platform-support-1&quot;&gt;Platform Support&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Native:&lt;/strong&gt; iOS, Android&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-Platform:&lt;/strong&gt; Flutter (with unified display across platforms)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ideal-use-cases-1&quot;&gt;Ideal Use Cases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Enterprise applications requiring 4K video support&lt;/li&gt;
&lt;li&gt;Professional video editing apps&lt;/li&gt;
&lt;li&gt;Social media platforms (especially in Asian markets)&lt;/li&gt;
&lt;li&gt;Streaming and content creation apps&lt;/li&gt;
&lt;li&gt;Apps requiring HDR video processing&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;limitations-1&quot;&gt;Limitations&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Documentation primarily in Chinese.&lt;/strong&gt; While Meishe has English documentation, many resources and examples are primarily available in Chinese, which can create barriers for Western development teams.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Less Western market presence.&lt;/strong&gt; Most case studies and customer examples are from Chinese companies, which can make it harder to assess fit for Western markets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Customization complexity.&lt;/strong&gt; Getting deep customization may require more direct engagement with Meishe’s team compared to more self-service SDKs.&lt;/p&gt;
&lt;h3 id=&quot;pricing--licensing-1&quot;&gt;Pricing / Licensing&lt;/h3&gt;
&lt;p&gt;Pricing information is not publicly available — you’ll need to contact Meishe directly for quotes. This is common for enterprise-focused SDKs but can slow down evaluation.&lt;/p&gt;
&lt;h3 id=&quot;who-its-for-1&quot;&gt;Who It’s For&lt;/h3&gt;
&lt;p&gt;Development teams (especially in Asian markets) building professional-grade video editing apps that need 4K support, HDR processing, and advanced real-time capabilities.&lt;/p&gt;
&lt;h3 id=&quot;how-it-compares-to-imgly&quot;&gt;How it compares to IMG.LY&lt;/h3&gt;
&lt;p&gt;Meishe offers strong professional-grade video processing with 4K and HDR support, particularly well-suited for Asian markets. IMG.LY provides similar 4K capabilities but adds cross-platform consistency (including web and server), template-driven automation, and a more developer-friendly integration experience for global teams. Meishe’s documentation is primarily in Chinese, while IMG.LY offers comprehensive English documentation and Western market support. If you’re building for Asian markets with HDR requirements, Meishe is a strong choice. For global deployment with automation needs, IMG.LY’s unified platform across client and server offers broader flexibility.&lt;/p&gt;
&lt;h2 id=&quot;3-byteplus-video-editor-sdk&quot;&gt;3. BytePlus Video Editor SDK&lt;/h2&gt;
&lt;p&gt;BytePlus Video Editor SDK comes from ByteDance (the company behind TikTok and CapCut). It’s designed to replicate the short-form, social-first editing experience that made those platforms successful, giving app developers similar workflows and effects libraries.&lt;/p&gt;
&lt;h3 id=&quot;core-capabilities-2&quot;&gt;Core Capabilities&lt;/h3&gt;
&lt;p&gt;BytePlus provides three main feature sets built around social media video creation:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Camera Feature:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real-time video capture and recording&lt;/li&gt;
&lt;li&gt;4K video recording on any mobile device&lt;/li&gt;
&lt;li&gt;Live filters and live beauty filters during capture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Preview Editor Feature:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Video editing and composition tools&lt;/li&gt;
&lt;li&gt;More than 80,000 effects and filters&lt;/li&gt;
&lt;li&gt;AR stickers and visual enhancements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Track Editor Feature:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Advanced multi-track timeline editing&lt;/li&gt;
&lt;li&gt;Speed change effects&lt;/li&gt;
&lt;li&gt;Transitions and audio features&lt;/li&gt;
&lt;li&gt;Multi-layer composition&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Export Options:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Support for 540p, 720p, 1080p, 4K resolutions&lt;/li&gt;
&lt;li&gt;Frame rates: 25fps, 30fps, 50fps, 60fps&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;platform-support-2&quot;&gt;Platform Support&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Native:&lt;/strong&gt; iOS (Cocoapods, SPM, Objective-C, SwiftUI), Android (Gradle, Java, Jetpack Compose)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-Platform:&lt;/strong&gt; React Native, Flutter&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Magic Template SDK:&lt;/strong&gt; Available for both platforms&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ideal-use-cases-2&quot;&gt;Ideal Use Cases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Social media apps focused on user-generated content&lt;/li&gt;
&lt;li&gt;Short-form video platforms&lt;/li&gt;
&lt;li&gt;Apps that want TikTok-like editing workflows&lt;/li&gt;
&lt;li&gt;Content creation tools with extensive effects libraries&lt;/li&gt;
&lt;li&gt;Apps requiring machine learning-powered visual enhancements&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;limitations-2&quot;&gt;Limitations&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Effects and beauty features are separate.&lt;/strong&gt; The standard and lite versions of the SDK don’t include Camera props, filters, and beauty effects — you need to purchase those separately, which can complicate pricing and integration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Customization beyond theming is limited.&lt;/strong&gt; While you can match the SDK to your brand through theming, deeper UI customization requires more effort compared to headless SDKs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Annual licensing only.&lt;/strong&gt; The SDK operates on an annual subscription basis, which may not work for all business models or early-stage products.&lt;/p&gt;
&lt;h3 id=&quot;pricing--licensing-2&quot;&gt;Pricing / Licensing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Annual subscription:&lt;/strong&gt; License valid for 1 year covering both iOS and Android&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contact sales&lt;/strong&gt; for pricing details&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration time:&lt;/strong&gt; Estimated 2 weeks per platform (iOS &amp;#x26; Android) for one developer&lt;/li&gt;
&lt;li&gt;Customization may extend integration timeline&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;who-its-for-2&quot;&gt;Who It’s For&lt;/h3&gt;
&lt;p&gt;Teams building social media-style video apps that want to replicate TikTok’s editing experience, especially if you need access to a massive effects library and ByteDance’s proven video editing architecture.&lt;/p&gt;
&lt;h3 id=&quot;how-it-compares-to-imgly-1&quot;&gt;How it compares to IMG.LY&lt;/h3&gt;
&lt;p&gt;BytePlus brings TikTok’s proven social video editing architecture with 80,000+ effects and filters, making it ideal for replicating short-form social experiences. IMG.LY takes a different approach: instead of a massive pre-built effects library, it focuses on customization, templates, and automation. BytePlus excels at client-side social video creation with extensive effects; IMG.LY excels at template-driven workflows, server-side automation, and cross-platform consistency (including web and Node.js). If you want TikTok-like effects out of the box, BytePlus delivers. If you need creative automation, programmatic content generation, or want to build custom workflows with full control over the editing experience, IMG.LY offers more flexibility and a dual-purpose architecture.&lt;/p&gt;
&lt;h2 id=&quot;4-banuba-video-editor-sdk&quot;&gt;4. Banuba Video Editor SDK&lt;/h2&gt;
&lt;p&gt;Banuba is a commercial video editing SDK focused on social media apps, with a strong emphasis on AR effects and beauty filters. It’s designed for teams building TikTok-like experiences or apps where user-generated video content is central to the product.&lt;/p&gt;
&lt;h3 id=&quot;core-capabilities-3&quot;&gt;Core Capabilities&lt;/h3&gt;
&lt;p&gt;Banuba provides a full-featured video editor with AI-powered tools and creative effects:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video Editing Features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Picture-in-picture for multi-video layouts&lt;/li&gt;
&lt;li&gt;AI-powered clipping that detects important segments&lt;/li&gt;
&lt;li&gt;Customizable templates with replaceable elements&lt;/li&gt;
&lt;li&gt;Trimming, merging, and timeline-based editing&lt;/li&gt;
&lt;li&gt;AI-generated captions (English, Mandarin, Spanish, Portuguese)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Creative Tools:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Face AR masks with morphing effects and animated backgrounds&lt;/li&gt;
&lt;li&gt;Video and transition effects (including “Rave” and “Cathode Flash”)&lt;/li&gt;
&lt;li&gt;Audio editing with recording and mixing&lt;/li&gt;
&lt;li&gt;Text and GIF overlays&lt;/li&gt;
&lt;li&gt;Beauty effects (skin smoothing, teeth whitening)&lt;/li&gt;
&lt;li&gt;Voice-change effects (Elf, Robot, Squirrel, Giant, Echo, Baritone)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Technical Details:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AI algorithms work offline — all processing happens on the device&lt;/li&gt;
&lt;li&gt;No user content transmitted to servers&lt;/li&gt;
&lt;li&gt;Requires OpenGL ES 3.0 minimum (3.1 for GPU-accelerated neural networks)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;platform-support-3&quot;&gt;Platform Support&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Native:&lt;/strong&gt; iOS 15.0+, Android 6.0+&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-Platform:&lt;/strong&gt; React Native, Flutter, NativeScript&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ideal-use-cases-3&quot;&gt;Ideal Use Cases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Social media applications with AR filters&lt;/li&gt;
&lt;li&gt;Photo/video editing apps for content creators&lt;/li&gt;
&lt;li&gt;E-commerce platforms with video demos&lt;/li&gt;
&lt;li&gt;Lifestyle and educational apps&lt;/li&gt;
&lt;li&gt;Short-form video content creation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;limitations-3&quot;&gt;Limitations&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Pricing complexity.&lt;/strong&gt; Banuba’s pricing is custom and depends on platform, features, custom development, and payment terms. You’ll need to contact sales for accurate quotes, which can slow down evaluation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AR licensing.&lt;/strong&gt; Face AR features are optional and may require separate licensing, adding complexity to your commercial agreement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Heavy focus on AR.&lt;/strong&gt; If you don’t need beauty filters or AR masks, you’re paying for features you won’t use.&lt;/p&gt;
&lt;h3 id=&quot;pricing--licensing-3&quot;&gt;Pricing / Licensing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Free trial:&lt;/strong&gt; 14 days with all features&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commercial:&lt;/strong&gt; Custom pricing based on platform, features, and usage&lt;/li&gt;
&lt;li&gt;Priced per platform per month&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;who-its-for-3&quot;&gt;Who It’s For&lt;/h3&gt;
&lt;p&gt;Teams building social media apps or content creation tools that need AR effects, beauty filters, and TikTok-like creative features out of the box.&lt;/p&gt;
&lt;h3 id=&quot;how-it-compares-to-imgly-2&quot;&gt;How it compares to IMG.LY&lt;/h3&gt;
&lt;p&gt;Banuba excels at AR effects and beauty filters for social media apps, while IMG.LY focuses on template-based workflows and cross-platform automation. If you need face masks and beauty effects, Banuba is purpose-built for that. But if you need server-side rendering, creative automation, or want the same editing engine across web, mobile, and backend, IMG.LY provides broader platform coverage and a dual-purpose architecture. Banuba is optimized for client-side social video creation; IMG.LY handles both interactive editing and programmatic content generation at scale.&lt;/p&gt;
&lt;h2 id=&quot;5-ffmpeg-via-ffmpegkit&quot;&gt;5. FFmpeg (via FFmpegKit)&lt;/h2&gt;
&lt;p&gt;FFmpeg is the industry-standard open-source tool for video processing. It’s not a video SDK in the traditional sense — it’s a command-line tool and set of libraries that handle encoding, decoding, transcoding, filtering, and nearly every video operation imaginable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FFmpegKit&lt;/strong&gt; is a wrapper that makes FFmpeg accessible on mobile platforms (Android, iOS, React Native, Flutter) through platform-specific APIs. It’s the primary way developers integrate FFmpeg into mobile apps.&lt;/p&gt;
&lt;h3 id=&quot;core-capabilities-4&quot;&gt;Core Capabilities&lt;/h3&gt;
&lt;p&gt;FFmpeg handles the entire video processing pipeline:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video Operations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Encoding and decoding (every codec imaginable)&lt;/li&gt;
&lt;li&gt;Format conversion (MP4, MOV, AVI, MKV, WebM, etc.)&lt;/li&gt;
&lt;li&gt;Trimming, cutting, merging, splitting&lt;/li&gt;
&lt;li&gt;Filtering and effects&lt;/li&gt;
&lt;li&gt;Subtitle burning&lt;/li&gt;
&lt;li&gt;Video stabilization&lt;/li&gt;
&lt;li&gt;Audio extraction and mixing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Technical Features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Command-line interface (via wrapper APIs)&lt;/li&gt;
&lt;li&gt;Support for hardware acceleration&lt;/li&gt;
&lt;li&gt;Concurrent command execution&lt;/li&gt;
&lt;li&gt;Extensive codec library&lt;/li&gt;
&lt;li&gt;Professional-grade output quality&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;platform-support-4&quot;&gt;Platform Support&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Native:&lt;/strong&gt; Android (Java API), iOS (Objective-C API), Linux (C++ API), macOS, tvOS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-Platform:&lt;/strong&gt; Flutter (Dart API), React Native (JavaScript API with TypeScript definitions)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Eight prebuilt packages:&lt;/strong&gt; Distributed via Maven Central, CocoaPods, pub, npm&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ideal-use-cases-4&quot;&gt;Ideal Use Cases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Backend video processing and transcoding&lt;/li&gt;
&lt;li&gt;Format conversion at scale&lt;/li&gt;
&lt;li&gt;Apps that need specific codec support&lt;/li&gt;
&lt;li&gt;Video compression and optimization&lt;/li&gt;
&lt;li&gt;Building custom video processing pipelines&lt;/li&gt;
&lt;li&gt;Server-side automation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;limitations-4&quot;&gt;Limitations&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;No UI whatsoever.&lt;/strong&gt; FFmpeg is a processing engine. If you want users to interact with video — timelines, filters, previews — you’ll need to build the entire interface yourself from scratch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Steep learning curve.&lt;/strong&gt; FFmpeg’s command-line syntax is powerful but complex. Even simple tasks require understanding flags, options, and codec parameters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GPL licensing complexity.&lt;/strong&gt; While FFmpegKit itself is LGPL v3.0, including GPL-licensed libraries makes the entire bundle GPL v3.0, which has implications for commercial apps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FFmpegKit has been officially retired.&lt;/strong&gt; As of June 2025, FFmpegKit is no longer maintained. Community-maintained forks exist, but official support is ending.&lt;/p&gt;
&lt;h3 id=&quot;pricing--licensing-4&quot;&gt;Pricing / Licensing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Free and open-source:&lt;/strong&gt; LGPL v3.0 for FFmpegKit library&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GPL v3.0&lt;/strong&gt; when GPL-licensed libraries are included&lt;/li&gt;
&lt;li&gt;Licensing depends on which codecs and libraries you bundle&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;who-its-for-4&quot;&gt;Who It’s For&lt;/h3&gt;
&lt;p&gt;Developers who need powerful backend video processing, format conversion, or transcoding — and are willing to build all user-facing features themselves. Not suitable for teams that need a ready-to-use editor.&lt;/p&gt;
&lt;h3 id=&quot;how-it-compares-to-imgly-3&quot;&gt;How it compares to IMG.LY&lt;/h3&gt;
&lt;p&gt;FFmpeg is a processing engine, not an editing SDK. It handles transcoding, format conversion, and video manipulation via command-line operations — but provides no UI, no templates, and no interactive editing components. IMG.LY includes a production-ready editor, template system, and user-facing tools out of the box, while also supporting server-side automation through Node.js. If you only need backend processing and are building all UI yourself, FFmpeg (via community forks) can work. If you need both interactive editing and automation, IMG.LY eliminates months of development by providing both in one platform. IMG.LY also avoids GPL licensing complications and the uncertainty of using a retired library with community forks.&lt;/p&gt;
&lt;h2 id=&quot;6-gstreamer-with-gstreamer-editing-services&quot;&gt;6. GStreamer (with GStreamer Editing Services)&lt;/h2&gt;
&lt;p&gt;GStreamer is a mature open-source multimedia framework used across Linux, Android, iOS, Windows, and macOS. It’s designed for building media applications — streaming, playback, recording, and non-linear editing — with a plugin-based architecture that makes it extremely flexible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GStreamer Editing Services (GES)&lt;/strong&gt; is a higher-level library built on top of GStreamer to simplify video editing application development. It was originally funded by Nokia for mobile video editing (“video editing in your pocket”).&lt;/p&gt;
&lt;h3 id=&quot;core-capabilities-5&quot;&gt;Core Capabilities&lt;/h3&gt;
&lt;p&gt;GStreamer provides the foundation for building multimedia applications:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Core Features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Audio and video playback, recording, streaming&lt;/li&gt;
&lt;li&gt;Non-linear editing capabilities via GES&lt;/li&gt;
&lt;li&gt;Extensive plugin library (encoders, decoders, filters)&lt;/li&gt;
&lt;li&gt;Hardware acceleration support&lt;/li&gt;
&lt;li&gt;Low-latency streaming (RTSP, WebRTC)&lt;/li&gt;
&lt;li&gt;Support for diverse codecs and formats&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;GStreamer Editing Services:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High-level API for creating editing applications&lt;/li&gt;
&lt;li&gt;Timeline-based editing&lt;/li&gt;
&lt;li&gt;Transition effects&lt;/li&gt;
&lt;li&gt;Audio mixing&lt;/li&gt;
&lt;li&gt;Asset management&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Performance Optimizations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Offloading work to specialized hardware (DSPs, GPUs)&lt;/li&gt;
&lt;li&gt;Low power consumption for embedded processors&lt;/li&gt;
&lt;li&gt;Support for ARM, MIPS, SPARC architectures&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;platform-support-5&quot;&gt;Platform Support&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cross-Platform:&lt;/strong&gt; Linux, Android, iOS, Windows, macOS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Language Bindings:&lt;/strong&gt; C++ (primary), Python, Java, JavaScript&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mobile:&lt;/strong&gt; Official SDK binaries for Android and iOS&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ideal-use-cases-5&quot;&gt;Ideal Use Cases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Custom video editing apps requiring deep control&lt;/li&gt;
&lt;li&gt;Streaming applications (RTSP, WebRTC)&lt;/li&gt;
&lt;li&gt;Media players and recording apps&lt;/li&gt;
&lt;li&gt;Research and academic projects&lt;/li&gt;
&lt;li&gt;Embedded systems and IoT devices&lt;/li&gt;
&lt;li&gt;Apps requiring specialized codec support&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;limitations-5&quot;&gt;Limitations&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Steep learning curve.&lt;/strong&gt; GStreamer’s plugin-based architecture is powerful but complex. Understanding pipelines, bins, and element linking takes time even for experienced developers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No pre-built UI.&lt;/strong&gt; GES provides editing APIs, but you’ll need to build the entire interface yourself. This means designing timelines, playback controls, effect panels, export dialogs — everything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Limited mobile-specific examples.&lt;/strong&gt; While GStreamer officially supports iOS and Android, production-ready mobile editing examples are scarce. You’ll be pioneering integration patterns yourself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integration complexity.&lt;/strong&gt; Getting GStreamer running on mobile requires compiling binaries, managing dependencies, and debugging platform-specific issues. This isn’t a “drop in and go” integration.&lt;/p&gt;
&lt;h3 id=&quot;pricing--licensing-5&quot;&gt;Pricing / Licensing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Free and open-source:&lt;/strong&gt; LGPL license&lt;/li&gt;
&lt;li&gt;GStreamer Editing Services included in main distribution&lt;/li&gt;
&lt;li&gt;No commercial support by default, but third-party companies (like Fluendo) offer commercial support and custom development&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;who-its-for-5&quot;&gt;Who It’s For&lt;/h3&gt;
&lt;p&gt;Teams with strong multimedia engineering expertise who need deep control over video processing and are willing to build everything from the ground up. Not suitable for teams that need fast time-to-market or lack video processing experience.&lt;/p&gt;
&lt;h3 id=&quot;how-it-compares-to-imgly-4&quot;&gt;How it compares to IMG.LY&lt;/h3&gt;
&lt;p&gt;GStreamer provides powerful low-level control over multimedia processing through its plugin-based architecture, making it ideal for specialized use cases like streaming, embedded systems, or custom pipelines. IMG.LY provides a higher-level, production-ready solution with UI components, templates, and automation built in. GStreamer requires significant expertise and months of development to build user-facing features; IMG.LY offers a working editor in hours with customization options that don’t require multimedia engineering expertise. If you need deep control over codecs, streaming protocols, or embedded deployment, GStreamer’s flexibility is unmatched. If you need to ship a production-ready video editor quickly with cross-platform support and automation capabilities, IMG.LY eliminates the complexity and provides enterprise support.&lt;/p&gt;
&lt;h2 id=&quot;comparison-table-video-sdks-for-mobile-applications&quot;&gt;Comparison Table: Video SDKs for Mobile Applications&lt;/h2&gt;












































































































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Feature / SDK&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Banuba&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Meishe&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;BytePlus&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;FFmpeg&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;GStreamer&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;IMG.LY SDK&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Platform Support&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;iOS 15+, Android 6+, React Native, Flutter, NativeScript&lt;/td&gt;&lt;td&gt;iOS, Android, Flutter&lt;/td&gt;&lt;td&gt;iOS, Android, React Native, Flutter&lt;/td&gt;&lt;td&gt;Android, iOS, React Native, Flutter, Linux, macOS&lt;/td&gt;&lt;td&gt;Linux, Android, iOS, Windows, macOS&lt;/td&gt;&lt;td&gt;Web, iOS, Android, React Native, Flutter, Desktop, Node.js&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;UI Included&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;✅ Pre-built editor&lt;/td&gt;&lt;td&gt;✅ Pre-built editor&lt;/td&gt;&lt;td&gt;✅ Pre-built editor&lt;/td&gt;&lt;td&gt;❌ Processing only&lt;/td&gt;&lt;td&gt;❌ GES provides APIs only&lt;/td&gt;&lt;td&gt;✅ Production-ready editor + Headless API&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Timeline Editing&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;✅ Yes&lt;/td&gt;&lt;td&gt;✅ Yes&lt;/td&gt;&lt;td&gt;✅ Multi-track&lt;/td&gt;&lt;td&gt;❌ Command-line only&lt;/td&gt;&lt;td&gt;✅ Via GES&lt;/td&gt;&lt;td&gt;✅ Advanced multi-track&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;AR / Beauty Filters&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;✅ Strong focus&lt;/td&gt;&lt;td&gt;✅ Real-time beauty&lt;/td&gt;&lt;td&gt;✅ 80,000+ effects&lt;/td&gt;&lt;td&gt;❌ None&lt;/td&gt;&lt;td&gt;❌ None&lt;/td&gt;&lt;td&gt;✅ Filters + extensible AI plugins&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Templates&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;✅ Customizable&lt;/td&gt;&lt;td&gt;⚠️ Limited info&lt;/td&gt;&lt;td&gt;✅ Magic Templates&lt;/td&gt;&lt;td&gt;❌ None&lt;/td&gt;&lt;td&gt;❌ None&lt;/td&gt;&lt;td&gt;✅ Advanced template system with variables&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Server-Side Automation&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;❌ No&lt;/td&gt;&lt;td&gt;❌ No&lt;/td&gt;&lt;td&gt;❌ No&lt;/td&gt;&lt;td&gt;✅ CLI-based&lt;/td&gt;&lt;td&gt;✅ Possible but complex&lt;/td&gt;&lt;td&gt;✅ Native support (same engine)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;AI Features&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;✅ AI clipping, captions&lt;/td&gt;&lt;td&gt;⚠️ Limited info&lt;/td&gt;&lt;td&gt;✅ ML-powered effects&lt;/td&gt;&lt;td&gt;❌ None&lt;/td&gt;&lt;td&gt;❌ None&lt;/td&gt;&lt;td&gt;✅ Native AI + model-agnostic plugins&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;4K Support&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;✅ Yes&lt;/td&gt;&lt;td&gt;✅ Up to 4K input&lt;/td&gt;&lt;td&gt;✅ 4K recording&lt;/td&gt;&lt;td&gt;✅ Yes&lt;/td&gt;&lt;td&gt;✅ Yes&lt;/td&gt;&lt;td&gt;✅ Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Customization Level&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;⚠️ UI/UX + optional API&lt;/td&gt;&lt;td&gt;⚠️ Requires team engagement&lt;/td&gt;&lt;td&gt;⚠️ Theming primarily&lt;/td&gt;&lt;td&gt;✅ Complete control&lt;/td&gt;&lt;td&gt;✅ Complete control&lt;/td&gt;&lt;td&gt;✅ Headless API + theming + UI config&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Integration Complexity&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;⚠️ Moderate (GitHub samples)&lt;/td&gt;&lt;td&gt;⚠️ Moderate&lt;/td&gt;&lt;td&gt;⚠️ Moderate (~2 weeks)&lt;/td&gt;&lt;td&gt;⚠️ Complex (retired library)&lt;/td&gt;&lt;td&gt;⚠️ Very complex&lt;/td&gt;&lt;td&gt;✅ Simple (hours to integrate)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Documentation&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;✅ English, developer portal&lt;/td&gt;&lt;td&gt;⚠️ Primarily Chinese&lt;/td&gt;&lt;td&gt;✅ English, comprehensive&lt;/td&gt;&lt;td&gt;✅ Extensive community docs&lt;/td&gt;&lt;td&gt;✅ Extensive but complex&lt;/td&gt;&lt;td&gt;✅ Comprehensive with samples&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Licensing / Pricing&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Custom (free 14-day trial)&lt;/td&gt;&lt;td&gt;Contact for quote&lt;/td&gt;&lt;td&gt;Annual subscription&lt;/td&gt;&lt;td&gt;Free, LGPL/GPL&lt;/td&gt;&lt;td&gt;Free, LGPL&lt;/td&gt;&lt;td&gt;Enterprise license&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Enterprise Support&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;⚠️ Custom agreements&lt;/td&gt;&lt;td&gt;⚠️ Direct engagement&lt;/td&gt;&lt;td&gt;⚠️ ByteDance support&lt;/td&gt;&lt;td&gt;❌ Community only&lt;/td&gt;&lt;td&gt;⚠️ Third-party (Fluendo)&lt;/td&gt;&lt;td&gt;✅ Dedicated support + SLAs&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Social media apps with AR focus&lt;/td&gt;&lt;td&gt;Professional apps in Asian markets&lt;/td&gt;&lt;td&gt;TikTok-like social video apps&lt;/td&gt;&lt;td&gt;Backend processing / transcoding&lt;/td&gt;&lt;td&gt;Custom multimedia apps&lt;/td&gt;&lt;td&gt;Cross-platform editing + automation at scale&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;making-your-decision-which-video-sdk-is-right-for-you&quot;&gt;Making Your Decision: Which Video SDK Is Right for You?&lt;/h2&gt;
&lt;p&gt;Here’s the honest breakdown based on your use case:&lt;/p&gt;
&lt;h3 id=&quot;choose-banuba-if&quot;&gt;Choose &lt;strong&gt;Banuba&lt;/strong&gt; if:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You’re building a social media app and AR effects are central to your product&lt;/li&gt;
&lt;li&gt;You need beauty filters, face masks, and creative effects out of the box&lt;/li&gt;
&lt;li&gt;Your primary audience expects TikTok-style creative tools&lt;/li&gt;
&lt;li&gt;You’re comfortable with custom pricing and optional feature licensing&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;choose-meishe-if&quot;&gt;Choose &lt;strong&gt;Meishe&lt;/strong&gt; if:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You’re targeting Asian markets where Meishe has strong adoption&lt;/li&gt;
&lt;li&gt;You need professional-grade 4K and HDR video processing&lt;/li&gt;
&lt;li&gt;Real-time preview and film-quality output are critical&lt;/li&gt;
&lt;li&gt;You have access to Chinese documentation resources or engineering support&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;choose-byteplus-if&quot;&gt;Choose &lt;strong&gt;BytePlus&lt;/strong&gt; if:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You want to replicate TikTok/CapCut’s editing experience&lt;/li&gt;
&lt;li&gt;You need access to 80,000+ effects and filters&lt;/li&gt;
&lt;li&gt;Machine learning-powered visual enhancements are important&lt;/li&gt;
&lt;li&gt;You’re comfortable with annual licensing and want ByteDance’s proven architecture&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;choose-ffmpeg-if&quot;&gt;Choose &lt;strong&gt;FFmpeg&lt;/strong&gt; if:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You need backend video processing, transcoding, or format conversion&lt;/li&gt;
&lt;li&gt;You’re building automation pipelines, not user-facing editors&lt;/li&gt;
&lt;li&gt;You have strong video engineering expertise&lt;/li&gt;
&lt;li&gt;You’re okay with command-line complexity and building all UI yourself&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Be aware:&lt;/strong&gt; FFmpegKit is officially retired; plan for community forks&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;choose-gstreamer-if&quot;&gt;Choose &lt;strong&gt;GStreamer&lt;/strong&gt; if:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You need deep control over multimedia processing&lt;/li&gt;
&lt;li&gt;You’re building custom streaming, playback, or editing applications&lt;/li&gt;
&lt;li&gt;You have strong multimedia engineering expertise&lt;/li&gt;
&lt;li&gt;You’re willing to invest significant time in integration and UI development&lt;/li&gt;
&lt;li&gt;You need specialized codec support or embedded system deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;choose-imgly-cesdk-if&quot;&gt;Choose &lt;strong&gt;IMG.LY CE.SDK&lt;/strong&gt; if:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You need both interactive editing and server-side automation&lt;/li&gt;
&lt;li&gt;Cross-platform consistency is critical (Web, iOS, Android, React Native, Flutter)&lt;/li&gt;
&lt;li&gt;You want a production-ready editor that integrates in hours, not months&lt;/li&gt;
&lt;li&gt;Template-based workflows and creative automation are important&lt;/li&gt;
&lt;li&gt;You need enterprise support, SLAs, and ongoing updates&lt;/li&gt;
&lt;li&gt;Your team wants to focus on product features, not rebuilding video editing infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;build-vs-buy-whats-the-real-cost&quot;&gt;Build vs Buy: What’s the Real Cost?&lt;/h2&gt;
&lt;p&gt;Let’s be honest about the engineering investment required:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Building on FFmpeg or GStreamer:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Months&lt;/strong&gt; to build a basic timeline editor with preview&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ongoing maintenance&lt;/strong&gt; for device compatibility, codec updates, performance optimization&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No UI components&lt;/strong&gt; — you’re building everything from scratch&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scaling challenges&lt;/strong&gt; when you need templates, automation, or new features&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Commercial SDKs (Banuba, Meishe, BytePlus):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Days to weeks&lt;/strong&gt; for basic integration&lt;/li&gt;
&lt;li&gt;Pre-built UI and effects libraries&lt;/li&gt;
&lt;li&gt;Dedicated support for troubleshooting&lt;/li&gt;
&lt;li&gt;Licensing costs vary; evaluate based on your revenue model&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;IMG.LY SDK:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hours&lt;/strong&gt; to integrate a working editor&lt;/li&gt;
&lt;li&gt;Production-ready UI with full customization&lt;/li&gt;
&lt;li&gt;Dual-purpose: client-side editing + server-side automation&lt;/li&gt;
&lt;li&gt;Template system and AI features included&lt;/li&gt;
&lt;li&gt;Enterprise support and regular updates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ask yourself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What’s your timeline?&lt;/strong&gt; If you need to ship in weeks, open-source frameworks won’t get you there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What’s your team’s expertise?&lt;/strong&gt; Video processing is complex; building from scratch requires specialized knowledge.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What happens when you scale?&lt;/strong&gt; Adding templates, automation, or AI features to a custom-built solution takes months of additional work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Commercial SDKs cost money upfront but save significant engineering time. Open-source tools are “free” but expensive in development hours and opportunity cost.&lt;/p&gt;
&lt;h2 id=&quot;conclusion-the-right-sdk-depends-on-your-goals&quot;&gt;Conclusion: The Right SDK Depends on Your Goals&lt;/h2&gt;
&lt;p&gt;There’s no one-size-fits-all answer, but here’s the summary:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FFmpeg and GStreamer&lt;/strong&gt; are powerful processing engines for developers who need backend automation or custom pipelines. They give you complete control but require significant expertise and offer no user-facing components. Great for transcoding, format conversion, and specialized workflows — not for building interactive editors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Banuba, Meishe, and BytePlus&lt;/strong&gt; are excellent for social media apps that need AR effects, beauty filters, and TikTok-style editing. They provide pre-built editors with extensive effects libraries, but customization beyond theming can be limited, and they’re focused primarily on client-side editing (no server automation).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IMG.LY CE.SDK&lt;/strong&gt; is the only solution that combines production-ready interactive editing with server-side creative automation. We provide cross-platform consistency, a complete template system, AI features, and enterprise support — all in one platform. Where other SDKs require you to choose between editing and automation, we give you both.&lt;/p&gt;
&lt;p&gt;If you’re building an app where video editing is a core feature (not a side project), and you need to ship fast without compromising on quality or scalability, our SDK eliminates months of development work and ongoing maintenance headaches.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The real question isn’t “which SDK is cheapest?”&lt;/strong&gt; It’s &lt;strong&gt;“how much engineering time will we spend building, maintaining, and scaling a custom solution — and what could we build instead if we had that time back?”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ready to see what IMG.LY SDK can do? &lt;a href=&quot;https://img.ly/showcases/cesdk&quot;&gt;Explore our demos&lt;/a&gt; or &lt;a href=&quot;https://img.ly/forms/contact-sales&quot;&gt;get in touch&lt;/a&gt; to discuss your project.&lt;/p&gt;</content:encoded><dc:creator>Klaudia</dc:creator><media:content url="https://blog.img.ly/2025/12/best-video-mobile-editor-sdk.jpg" medium="image"/><category>Video Editor</category><category>Mobile App Development</category><category>Insights</category></item><item><title>CE.SDK v1.19 Release Notes</title><link>https://img.ly/blog/creative-editor-sdk-v1-19-release-notes/</link><guid isPermaLink="true">https://img.ly/blog/creative-editor-sdk-v1-19-release-notes/</guid><description>Explore unified editing of your graphic blocks, new showcases, and an exciting announcement for video!</description><pubDate>Fri, 22 Dec 2023 14:26:48 GMT</pubDate><content:encoded>&lt;p&gt;Since our last release, we’ve been crafting new features to empower your users’ creative journey. Today, we’re thrilled to introduce CE.SDK v1.19! With this release, you can:&lt;/p&gt;
&lt;h3 id=&quot;unify-design-power-with-block-unification&quot;&gt;Unify Design Power with Block Unification&lt;/h3&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/1-19/block-unification_1-19-videofills.mp4&quot; controls muted playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Introducing Block Unification for CE.SDK on Mobile and Web—a feature that simplifies design versatility. Videos, images, shapes, and stickers now seamlessly come together as one Graphic Block, easily modified within a unified inspector. All supported settings for your selected block, including strokes, filters, and adjustments, are conveniently displayed in a single, user-friendly space.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unified Graphic Block&lt;/strong&gt;&lt;br&gt;
Experience a singular Graphic Block supporting diverse fills (images, videos, colors, gradients) and shapes. Switch fill types (video, image, color) effortlessly, enabling dynamic designs in one place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Easy Fine-Tuning&lt;/strong&gt;&lt;br&gt;
Block Unification integrates effects, filters, adjustments, and blur uniformly across all design elements. Elevate your designs with precision and consistency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A Treat for Users and Developers&lt;/strong&gt;&lt;br&gt;
For developers, Block Unification means streamlined code maintenance. The consolidation of design blocks into one Graphic Block simplifies development, allowing more focus on crafting exceptional design experiences with IMG.LY.&lt;/p&gt;
&lt;h3 id=&quot;enhance-interaction-with-mouse-wheel-support&quot;&gt;Enhance Interaction with Mouse Wheel Support&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;mousewheel.jpg&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1480px) 1480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1480&quot; height=&quot;643&quot; src=&quot;https://img.ly/_astro/mousewheel_ZdLdi7.webp&quot; srcset=&quot;/_astro/mousewheel_2evOYG.webp 640w, /_astro/mousewheel_1btqKu.webp 750w, /_astro/mousewheel_Z1mpLwI.webp 828w, /_astro/mousewheel_1aUQbe.webp 1080w, /_astro/mousewheel_t4NVy.webp 1280w, /_astro/mousewheel_ZdLdi7.webp 1480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;In our latest update, our engine now directly registers mouse wheel events on the canvas, eliminating the need for manual camera manipulation. This seamless integration brings a host of new features, including smooth zooming, centered around the cursor, and support for pinch gestures on multitouch trackpads. Enjoy a more intuitive and unified interaction experience across various inputs, simplifying and enriching your design process.&lt;/p&gt;
&lt;h3 id=&quot;migrate-to-cesdk-v119&quot;&gt;Migrate to CE.SDK v1.19&lt;/h3&gt;
&lt;p&gt;The structural changes of CE.SDK v1.19 bring a more composable and powerful editing experience. To benefit from our new features, and to attain to licensing changes, head to our &lt;a href=&quot;https://img.ly/docs/cesdk/js/upgrade-4f8715/&quot;&gt;documentation on migrating to v.1.19&lt;/a&gt; for an easy transition.&lt;/p&gt;
&lt;p&gt;Wait. There’s more. As we explore the extensive capabilities of our SDK, we’re unveiling new showcases to demonstrate your possibilities. Plus, stick around until the end for an exciting announcement that will elevate your video creation experience!&lt;/p&gt;
&lt;h3 id=&quot;new-showcase-automated-resizing&quot;&gt;New Showcase: Automated Resizing&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;Scale your marketing materials across all platforms with automated sizes.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1480px) 1480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1480&quot; height=&quot;643&quot; src=&quot;https://img.ly/_astro/automated_Z1or62o.webp&quot; srcset=&quot;/_astro/automated_1UVbIY.webp 640w, /_astro/automated_1iPdUS.webp 750w, /_astro/automated_1uk67P.webp 828w, /_astro/automated_DqpQ8.webp 1080w, /_astro/automated_2a5INP.webp 1280w, /_astro/automated_Z1or62o.webp 1480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;In our new live &lt;a href=&quot;https://img.ly/showcases/cesdk/automated-resizing/web&quot;&gt;showcase&lt;/a&gt; for web, effortlessly generate size variations of your designs and seamlessly &lt;strong&gt;scale your&lt;/strong&gt; &lt;strong&gt;marketing materials across platforms&lt;/strong&gt;. The automated resizing feature empowers you to easily leverage marketing materials without requiring the involvement of a design team, ensuring efficiency and adaptability.&lt;/p&gt;
&lt;h3 id=&quot;new-showcase-version-history&quot;&gt;New Showcase: Version History&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;Keep track! Version History monitors changes made to your design.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1480px) 1480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1480&quot; height=&quot;643&quot; src=&quot;https://img.ly/_astro/history_Z1I9vf0.webp&quot; srcset=&quot;/_astro/history_1suucg.webp 640w, /_astro/history_ZuG7lP.webp 750w, /_astro/history_1T8alE.webp 828w, /_astro/history_Z1gpSmo.webp 1080w, /_astro/history_Z1uhGNc.webp 1280w, /_astro/history_Z1I9vf0.webp 1480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Discover the convenience of Version History, enabling you to monitor changes made to each design and seamlessly restore past versions when needed. Explore our straightforward &lt;a href=&quot;https://img.ly/showcases/cesdk/version-history/web&quot;&gt;Version History&lt;/a&gt; showcase for a practical design experience.&lt;/p&gt;
&lt;h3 id=&quot;outlook-offer-tiktok-inspired-video-editing&quot;&gt;Outlook: Offer TikTok-Inspired Video Editing&lt;/h3&gt;
&lt;p&gt;Video remains the reigning champion for attracting and retaining your audience. Yet, building a video editor from the ground up is a pain.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/1-19/HD_IMGLY-SDK_141223-0.mp4&quot; controls playsinline poster=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/1-19/thumb-vcc.jpg&quot;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;To simplify your journey and save valuable time and resources, we are excited to unveil a groundbreaking SDK for creating captivating short-form videos in January 2024!&lt;/p&gt;
&lt;p&gt;Empower your users to arrange video, audio, text, and graphics seamlessly on a sleek video timeline. Our new camera introduces popular features like Voiceover, Zoom, Tap to Record, and more. Plus, bring the beloved Split Screen Modes for Reactions and Duets, akin to TikTok and Instagram!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://share.hsforms.com/1mrIXiBbURn6sMqYgZG9c6A1hk3i&quot;&gt;Be the first to access our new IMG.LY SDK—join our waitlist now!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading!&lt;/strong&gt; &lt;a href=&quot;https://share.hsforms.com/1IgAOV1wASXGPnFG4ZPLejg1hk3i&quot;&gt;&lt;strong&gt;Subscribe&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;to our newsletter to stay in the loop with the latest news and updates.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Neslihan</dc:creator><media:content url="https://blog.img.ly/2023/12/v119.jpg" medium="image"/><category>Release Notes</category><category>CE.SDK</category><category>Video Editing</category><category>Design Editor</category><category>App Development</category><category>Mobile App Development</category></item><item><title>CE.SDK v1.12 Release</title><link>https://img.ly/blog/creative-editor-sdk-v_1_12_0-release-notes/</link><guid isPermaLink="true">https://img.ly/blog/creative-editor-sdk-v_1_12_0-release-notes/</guid><description>Effortlessly auto-resize designs, enhance readability, achieve perfect alignment, and more! With CE.SDK v1.12.</description><pubDate>Thu, 15 Jun 2023 12:19:24 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://img.ly/blog/creative-editor-sdk-v_1_11_0-release-notes/&quot;&gt;In our last release&lt;/a&gt;, we introduced exciting features such as cutout stickers and shapes, letter case options, effortless layouting, and more.&lt;br&gt;
Now, get ready for CE.SDK v1.12, packed with more features and essential fixes to enhance your creative editing experience. Let’s explore what’s included:&lt;/p&gt;
&lt;h3 id=&quot;auto-resize-designs&quot;&gt;Auto-Resize Designs&lt;/h3&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/1-12/template-page-resize.mp4&quot; controls autoplay muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interfaces:&lt;/strong&gt; Advanced UI, API&lt;br&gt;
&lt;strong&gt;Platforms:&lt;/strong&gt; All&lt;br&gt;
Easily auto-resize or adapt your designs to different page sizes without the need for separate templates. Our intelligent solution adjusts the elements of your design while preserving full-size elements and optimizing image fill modes. This time-saving feature empowers you to reuse templates with ease, ensuring consistent and professional results. Whether it’s creating graphics for social media, presentations, or printed materials, our auto-resize feature simplifies the process.&lt;br&gt;
Learn more in our &lt;a href=&quot;https://img.ly/docs/cesdk/js/use-templates/apply-template-35c73e/&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;enhance-readability-with-paragraphs&quot;&gt;Enhance Readability with Paragraphs&lt;/h3&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/1-12/paragraph.mp4&quot; controls autoplay muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interfaces:&lt;/strong&gt; Default UI, Advanced UI, API&lt;br&gt;
&lt;strong&gt;Platforms:&lt;/strong&gt; All&lt;br&gt;
Achieve better text readability and design aesthetics by modifying the &lt;strong&gt;spacing between paragraphs&lt;/strong&gt;. This feature seamlessly integrates with our advanced text styling options, providing a comprehensive toolkit for creating visually stunning text-centric products, such as postcards and more. Learn more about paragraph spacing in our &lt;a href=&quot;https://img.ly/docs/cesdk/js/concepts/blocks-90241e/&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;avoid-cut-off-text&quot;&gt;Avoid Cut Off Text&lt;/h3&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/1-12/cutoff.mp4&quot; controls autoplay muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interfaces:&lt;/strong&gt; Default UI, Advanced UI, API&lt;br&gt;
&lt;strong&gt;Platforms:&lt;/strong&gt; All&lt;br&gt;
We’ve made a significant improvement related to &lt;strong&gt;text clipping&lt;/strong&gt; that ensures the top of the text is no longer cut off. This enhancement greatly improves the visual presentation and communication of your content, providing a seamless user experience.&lt;/p&gt;
&lt;h3 id=&quot;achieve-perfect-design-alignment&quot;&gt;Achieve Perfect Design Alignment&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;Move and snap elements for easy alignment.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1480px) 1480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1480&quot; height=&quot;643&quot; src=&quot;https://img.ly/_astro/Snapping_1DFItd.webp&quot; srcset=&quot;/_astro/Snapping_2qTVjb.webp 640w, /_astro/Snapping_lAufs.webp 750w, /_astro/Snapping_stmkR.webp 828w, /_astro/Snapping_ZxkQ8u.webp 1080w, /_astro/Snapping_xEVam.webp 1280w, /_astro/Snapping_1DFItd.webp 1480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interfaces:&lt;/strong&gt; Default UI, Advanced UI, Touch UI&lt;br&gt;
&lt;strong&gt;Platforms:&lt;/strong&gt; All&lt;br&gt;
We’ve addressed the problem of &lt;strong&gt;snapping&lt;/strong&gt; to the wrong elements, ensuring a more accurate and reliable snapping experience. Precise alignment is crucial in design, and our continuous improvement efforts will further enhance snapping capabilities in future updates. Enjoy a seamless and frustration-free design process with our enhanced snapping precision.&lt;/p&gt;
&lt;h3 id=&quot;take-control-of-text-editing-with-precision&quot;&gt;Take Control of Text Editing with Precision&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;Retrieve the cursor range, and precisely insert content exactly where you need it.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1480px) 1480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1480&quot; height=&quot;643&quot; src=&quot;https://img.ly/_astro/CursorRange-mouse_B8Js2.webp&quot; srcset=&quot;/_astro/CursorRange-mouse_1VKirs.webp 640w, /_astro/CursorRange-mouse_1x119s.webp 750w, /_astro/CursorRange-mouse_22qYUt.webp 828w, /_astro/CursorRange-mouse_Z1Kt3up.webp 1080w, /_astro/CursorRange-mouse_1WUTSh.webp 1280w, /_astro/CursorRange-mouse_B8Js2.webp 1480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interfaces:&lt;/strong&gt; API&lt;br&gt;
&lt;strong&gt;Platforms:&lt;/strong&gt; All&lt;br&gt;
&lt;code&gt;getTextCursorRange&lt;/code&gt; empowers you to access the current cursor range within the text editor. This feature opens up a world of possibilities, whether you’re building a custom user interface or implementing text-related automation. You can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Query the text cursor offset and the current selected text range.&lt;/li&gt;
&lt;li&gt;Automate text generation and insertion with precision.&lt;/li&gt;
&lt;li&gt;Seamlessly integrate this functionality into your workflow and unlock new possibilities for your text-centric projects.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Say goodbye to guesswork and welcome a more accurate and efficient text editing experience. Retrieve the current cursor range effortlessly, and precisely insert content exactly where you need it. Learn more in our &lt;a href=&quot;https://img.ly/docs/cesdk/js/text/edit-c5106b/&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;manage-templates-from-multiple-sources&quot;&gt;Manage Templates from Multiple Sources&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;Effortlessly browse and query a vast database of templates.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1480px) 1480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1480&quot; height=&quot;643&quot; src=&quot;https://img.ly/_astro/templates_Z1pGJeP.webp&quot; srcset=&quot;/_astro/templates_Z1veuU9.webp 640w, /_astro/templates_Z28ksIf.webp 750w, /_astro/templates_Z1VPAwi.webp 828w, /_astro/templates_CaLDG.webp 1080w, /_astro/templates_28P5Bo.webp 1280w, /_astro/templates_Z1pGJeP.webp 1480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interfaces:&lt;/strong&gt; API&lt;br&gt;
&lt;strong&gt;Platforms:&lt;/strong&gt; All&lt;br&gt;
Introducing an improved template configuration feature that allows you to &lt;strong&gt;define asset sources for templates&lt;/strong&gt;. Now, you can effortlessly browse and query a vast database of templates, opening up a world of possibilities for your projects.&lt;/p&gt;
&lt;p&gt;By aligning the template configuration process with the existing asset management workflow, we’ve streamlined the experience, saving you time and effort. Moreover, you have the flexibility to define remote asset sources directly from &lt;strong&gt;databases&lt;/strong&gt;, expanding your template options even further.&lt;/p&gt;
&lt;p&gt;Enjoy a more streamlined workflow with CE.SDK v1.12, enhanced visual presentation, and the freedom to customize your designs with ease.&lt;br&gt;
&lt;a href=&quot;https://img.ly/forms/free-trial&quot;&gt;Get started for free&lt;/a&gt; and bring creativity to your users!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://share.hsforms.com/1IgAOV1wASXGPnFG4ZPLejg1hk3i&quot;&gt;&lt;strong&gt;Subscribe to our Newsletter&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;to stay in the loop with the latest news and updates. Thank you for choosing IMG.LY&lt;/strong&gt;.&lt;/p&gt;</content:encoded><dc:creator>Neslihan</dc:creator><media:content url="https://blog.img.ly/2023/06/cesdk-imgly.jpg" medium="image"/><category>Release Notes</category><category>Creative Editor</category><category>Web Application</category><category>Mobile App Development</category></item><item><title>IMG.LY Partners with Soundstripe to Infuse Video Editing with Epic Royalty-Free Music &amp; SFX</title><link>https://img.ly/blog/img-ly-partners-with-soundstripe/</link><guid isPermaLink="true">https://img.ly/blog/img-ly-partners-with-soundstripe/</guid><description>Elevated storytelling and captivating viewers is everything: unlock the ultimate video editing experience with this integration.</description><pubDate>Wed, 24 May 2023 07:31:24 GMT</pubDate><content:encoded>&lt;p&gt;We are excited to announce our exciting new partnership with Soundstripe, the leading provider of royalty-free music for video creators. Our VideoEditor SDK now features a seamless integration with Soundstripe, offering users access to a vast collection of over &lt;strong&gt;9,000 hand-curated songs&lt;/strong&gt; from &lt;strong&gt;150+ musicians&lt;/strong&gt;, along with an extensive library of &lt;strong&gt;70,000 sound effects&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/soundstripe_2.mp4&quot; controls playsinline poster=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/integrate-soundstripe-into-app.jpg&quot;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Starting from version 11, our &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;VideoEditor SDK&lt;/a&gt; now comes pre-equipped with a seamless Soundstripe integration, as detailed in our &lt;a href=&quot;https://img.ly/docs/vesdk/ios/guides/audio-overlays/custom-overlays/soundstripe-integration/#soundstripe-api&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;streamlining-music-licensing-for-digital-platforms&quot;&gt;Streamlining Music Licensing for Digital Platforms&lt;/h3&gt;
&lt;p&gt;Finding the right music for digital platforms hasn’t been easy. Music licensing complexities and the challenge of finding high-quality tracks that align with your brand standards can be overwhelming.&lt;/p&gt;
&lt;p&gt;But now, with our &lt;a href=&quot;https://www.soundstripe.com/blogs/how-to-use-soundstripe-video-0&quot;&gt;partnership with Soundstripe&lt;/a&gt;, you can effortlessly enhance your app with royalty-free music and video editing.&lt;/p&gt;
&lt;p&gt;Our mission has always been to provide the ultimate toolkit for building captivating creative experiences, and video editing plays a central role in achieving this. We recognize the significant role that music plays in video storytelling, as it has the power to evoke emotions, set the mood, tone, and atmosphere, and greatly influence the pacing and energy of a video. With Soundstripe’s diverse range of over 50 genres, your users will have no trouble finding the perfect track to complement their visual narrative.&lt;/p&gt;
&lt;h3 id=&quot;effortless-music-selection&quot;&gt;Effortless Music Selection&lt;/h3&gt;
&lt;p&gt;We have developed an &lt;strong&gt;intuitive search&lt;/strong&gt; interface that allows users to explore music by &lt;strong&gt;title, genre, and description&lt;/strong&gt; effortlessly. Once users have found their desired song, they can easily position any section of the track over their video. Furthermore, we are delighted to provide every one of our customers with seven complimentary sample songs, allowing them to get a taste of the fantastic library available. To unlock the full audio library, simply head over to Soundstripe and &lt;a href=&quot;https://www.soundstripe.com/enterprise-licensing&quot;&gt;request an API key&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We are eagerly looking forward to working closely with Soundstripe to deliver the most exceptional video editing experience on the market. We invite you to try out &lt;a href=&quot;https://img.ly/video-sdk&quot;&gt;VideoEditor SDK&lt;/a&gt; in your Android or iOS app and witness the transformative impact it will have on your users’ creativity. Together, we can take your video editing capabilities to new heights.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading! Stay ahead of the curve with our newsletter and &lt;a href=&quot;https://share.hsforms.com/1IgAOV1wASXGPnFG4ZPLejg1hk3i&quot;&gt;sign up&lt;/a&gt; now!&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Jan</dc:creator><media:content url="https://blog.img.ly/2023/06/soundstripe-app.jpg" medium="image"/><category>Video App</category><category>Royalty-Free Music</category><category>App Development</category><category>Mobile App Development</category><category>Video Editing</category><category>Company</category></item><item><title>Why Mobile-First is the Future of Print</title><link>https://img.ly/blog/why-mobile-first-is-the-future-of-print/</link><guid isPermaLink="true">https://img.ly/blog/why-mobile-first-is-the-future-of-print/</guid><description>A seamless mobile experience is not the only crucial aspect to consider. Investing in app development is the key to staying ahead.</description><pubDate>Thu, 23 Feb 2023 13:11:09 GMT</pubDate><content:encoded>&lt;p&gt;Over the last 30 years, mobile phones have heavily influenced lifestyles across the globe. They have changed the way we communicate, spend leisure time, look for information, and shop.&lt;/p&gt;
&lt;p&gt;Thanks to its accessibility and powerful capabilities, mobile has eclipsed the desktop. Statista reported last year, &lt;strong&gt;59% of all global web traffic came from mobile&lt;/strong&gt;, with almost five billion people accessing the internet through mobile devices. Moreover, since 2018, app usage has exceeded browser usage on mobile devices, with &lt;strong&gt;shopping apps noting 49% year-over-year usage increase&lt;/strong&gt; between 2020 &amp;#x26; 2021.&lt;/p&gt;
&lt;p&gt;This means companies have to not only provide a seamless mobile experience if they wish to attract and convert users, but also invest in app development to stay ahead of the curve.&lt;/p&gt;
&lt;h2 id=&quot;market-trends&quot;&gt;Market Trends&lt;/h2&gt;
&lt;p&gt;The printing industry is showing no signs of slowing down. In 2021, the global print-on-demand market was valued at $4.90 billion, and it’s projected to generate $39.40 billion in revenue by 2030, according to industry reports from &lt;a href=&quot;https://www.grandviewresearch.com/industry-analysis/print-on-demand-market-report&quot;&gt;Grand View Research&lt;/a&gt;. The report also suggests that print-on-demand is expected to grow at a compound annual growth rate of 26.1% from 2022 to 2030, indicating significant potential for further expansion.&lt;/p&gt;
&lt;p&gt;However, print-on-demand is not the only promising sector to watch. The Print Advertising segment is projected to have ad spending worth $46.87 billion in 2023. This sector has remained resilient to the global decline in the print industry during the Covid-19 pandemic, registering an 18% revenue increase in the US between 2019 and 2020.&lt;/p&gt;
&lt;p&gt;With the printing industry’s rapid growth, competition is likely to increase, making it essential for companies to prioritize excellence in order to attract and retain customers.&lt;/p&gt;
&lt;h2 id=&quot;user-preference&quot;&gt;User Preference&lt;/h2&gt;
&lt;p&gt;It’s undeniable that mobile is becoming increasingly dominant in e-commerce. According to &lt;a href=&quot;https://www.shopify.com/blog/mobile-commerce&quot;&gt;Shopify&lt;/a&gt;, &lt;strong&gt;almost 50% of all e-commerce transactions are predicted to occur via mobile devices by 2024, with an expected revenue of $620.97 billion&lt;/strong&gt;. Being able to quickly and effectively serve customers on mobile is now essential for businesses looking to thrive. In 2022, Shopify launched &lt;a href=&quot;https://shop.app/&quot;&gt;shop.app&lt;/a&gt;, a mobile-only e-commerce platform, raising the bar for the industry. To stay competitive, other e-commerce businesses will need to develop similarly sophisticated solutions for mobile users.&lt;/p&gt;
&lt;p&gt;Having a seamless mobile experience is also an important factor for B2B companies. Although most interactions still occur on desktop, modern working arrangements allow people to work on the go, without being limited to one device. Providing customers with flexible options is a significant advantage. For example, a web-to-print editor for print advertising should be cross-platform, providing the same experience across all devices during design creation and print validation processes. Offering agile solutions ahead of industry standards can position a company as a go-to service provider. However, an excellent editor alone is not enough; covering the basics of a good mobile experience is crucial.&lt;/p&gt;
&lt;h2 id=&quot;game-changing-mobile-factors&quot;&gt;Game-Changing Mobile Factors&lt;/h2&gt;
&lt;p&gt;Customers are becoming more unforgiving when it comes to poor user experience. According to Google, &lt;strong&gt;61% of mobile users won’t return to a site they had trouble accessing&lt;/strong&gt;, with 40% turning to a competitor’s site instead. For print companies relying almost exclusively on online purchases, this poses a substantial threat, hence, the need to fulfill a long list of expectations to satisfy users.&lt;/p&gt;
&lt;p&gt;While the ultimate recommendation is to build a mobile app, customers will most likely want to check out the web page first, hence, the importance of providing a spotless web experience.&lt;/p&gt;
&lt;p&gt;Here are some of the most prominent factors of mobile UX to optimize.&lt;/p&gt;
&lt;h3 id=&quot;design-editing&quot;&gt;Design Editing&lt;/h3&gt;
&lt;p&gt;While for most e-commerce stores developing a smooth purchase experience is rather straightforward, many printing companies have an added layer of complication - catering to custom-made products. Once a rarity, personalization of products has proven to be a constantly growing trend many companies choose to embrace, and rightly so. The global Custom T-shirt Printing market alone was &lt;a href=&quot;https://www.credenceresearch.com/report/custom-t-shirt-printing-market&quot;&gt;valued at $3.7 billion&lt;/a&gt; in 2021 and is projected to reach $6.98 billion by 2028.&lt;/p&gt;
&lt;p&gt;With a growing demand for custom prints, follows a necessity for a web-to-print editor that will provide a seamless design process - on both desktop and mobile. Web-to-print editor simplifies artwork supply, jumpstarts the creative process, and takes care of print validation. The ideal tool should be intuitive to navigate and have a quick load time while providing a quality design experience. The same approach should be adopted by print service providers, breaching a gap between multiple software and communication channels clients have to endure.&lt;/p&gt;
&lt;p&gt;A powerful web-to-print editor doesn’t just enhance the user experience on your website - it can also open new doors for your business, such as scaling to native mobile apps, print kiosks, and terminals. Developing separate tools for each platform can be time-consuming and costly. With ready-made solutions (as we will discover below), you can speed up the development process and achieve a seamless editing experience across all devices without starting from scratch each time.&lt;/p&gt;
&lt;h3 id=&quot;site-speed&quot;&gt;Site Speed&lt;/h3&gt;
&lt;p&gt;In a study conducted by &lt;a href=&quot;https://www2.deloitte.com/content/dam/Deloitte/ie/Documents/Consulting/Milliseconds_Make_Millions_report.pdf&quot;&gt;Deloitte&lt;/a&gt;, they observed that a &lt;strong&gt;difference of 0.1 seconds has improved conversion rate by 8% and increased purchase value by 10% in the retail sector&lt;/strong&gt;. The highest conversion rate occurs on pages with a load time of 0-2 seconds, and as reported by Google, 53% of users abandon the page if the load time takes more than 3 seconds. Moreover, it has been estimated that slow-loading websites cost retailers &lt;a href=&quot;https://www.thinkwithgoogle.com/consumer-insights/consumer-trends/mobile-site-load-time-statistics/&quot;&gt;$2.6 billion in lost sales&lt;/a&gt; every year.&lt;/p&gt;
&lt;p&gt;It’s also worth mentioning that Core Web Vitals monitoring page speed is a Google ranking factor. Poor page speed performance will in turn take a hit on rankings - and with a loss of each position on the Search Engine Result Page, the click-through rate falls significantly.&lt;/p&gt;
&lt;p&gt;Investing in optimizing page speed and providing a smooth user experience is a win-win on every front.&lt;/p&gt;
&lt;h3 id=&quot;mobile-first-design&quot;&gt;Mobile-First Design&lt;/h3&gt;
&lt;p&gt;Responsive web design is not enough anymore. Websites have to be built with a mobile-first approach in mind. What’s the difference? Responsive design ensures that a page can be viewed on all devices, changing its dimensions to fit different screen sizes. The content and the layout of the page, however, remain unchanged.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Responsive design adapts to screens, but native mobile-first considers content &amp;amp;#x26; layout for optimal mobile experience, as seen in the image — CE.SDK Apparel UI for Android, iOS, and web.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1280px) 1280px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1280&quot; height=&quot;720&quot; src=&quot;https://img.ly/_astro/mobile-optimization-print-app-creative-editor-for-print-app-FORMATS_ZnW1bb.webp&quot; srcset=&quot;/_astro/mobile-optimization-print-app-creative-editor-for-print-app-FORMATS_Z1BiYyN.webp 640w, /_astro/mobile-optimization-print-app-creative-editor-for-print-app-FORMATS_15uue8.webp 750w, /_astro/mobile-optimization-print-app-creative-editor-for-print-app-FORMATS_Z9nM8I.webp 828w, /_astro/mobile-optimization-print-app-creative-editor-for-print-app-FORMATS_Z1YshHq.webp 1080w, /_astro/mobile-optimization-print-app-creative-editor-for-print-app-FORMATS_ZnW1bb.webp 1280w&quot;&gt;&lt;/p&gt;
&lt;p&gt;While responsive web design is just a part of the design workflow making sure a website is accessible for mobile, mobile-first &lt;em&gt;defines&lt;/em&gt; the design approach and focuses on creating a website that is intended for mobile users. It means that every aspect of mobile interaction is taken into account when designing a page.&lt;/p&gt;
&lt;p&gt;While the general layout of a page is important for both desktop and mobile, clickable elements of design such as buttons and navigation menus can have larger importance for mobile users. Navigating with a thumb or a finger poses more challenges than a neat computer cursor. The mobile-first design makes sure that every part of the page - content, media, navigation, and calls-to-action, feels intuitive to navigate and use. However, even the best mobile-first web design can’t compare to native mobile apps, which usability and performance are far superior.&lt;br&gt;
Hence, mobile-first should be a necessity embraced by all, those who want to go a step further and provide a fully optimized user experience should turn their eyes to app development.&lt;/p&gt;
&lt;h3 id=&quot;payments&quot;&gt;Payments&lt;/h3&gt;
&lt;p&gt;The simplicity of mobile payments is yet another reason why mobile purchases are on the rise. Mobile wallet solutions like Google Pay, Apple Pay, and Samsung Pay allow users to complete a transaction with just one click, removing the need for credit card input.&lt;/p&gt;
&lt;p&gt;Nowadays, providing an easy and quick checkout experience is more important than ever. Over the last few years, the cart abandonment rate significantly increased, from 59.80% back in 2006 to 88.05% in 2020. Focusing on optimizing the checkout and payment processes could reduce the risk of not completing a purchase. A great example of a seamless payment experience is  Shopify’s Shop.app with a one-tap checkout, making it as easy as possible for users to convert.&lt;/p&gt;
&lt;p&gt;The checkout funnel is also a great space to utilize personalization hacks and recommend similar products. &lt;a href=&quot;https://instapage.com/blog/personalization-statistics&quot;&gt;Instapage&lt;/a&gt; reported that &lt;strong&gt;shopping cart recommendations influence 92% of users&lt;/strong&gt;, making it a great tool to increase basket size.&lt;/p&gt;
&lt;h2 id=&quot;beyond-browser---investing-in-mobile-apps&quot;&gt;Beyond Browser - Investing in Mobile Apps&lt;/h2&gt;
&lt;p&gt;Mobile app downloads increased by over 80% since 2016, reaching a mighty 255 billion in 2022. The growing popularity of mobile apps is quite understandable, apps provide a better native experience, are easier to navigate, and usually provide personalized recommendations and remember users’ last activity (&lt;a href=&quot;https://firstsiteguide.com/mobile-traffic-stats/&quot;&gt;58% of users consider this important&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;In-app shopping, when correctly optimized, is quick and easy, with enhanced product presentation, intuitive navigation, and no unnecessary hassle of filling in forms and adding credit card details.&lt;/p&gt;
&lt;p&gt;Print is once again one of the industries that could leverage all advantages mobile apps have to offer. An example of a company that did it right is IMG.LY’s customer &lt;a href=&quot;https://www.shutterfly.com/mobile/&quot;&gt;Shutterfly&lt;/a&gt;, which combined the emerging trends of personalization and going mobile. The company developed an app that automatically creates personalized items users can purchase. They simply ask for permission to access user’s photo gallery, identify pictures with faces and apply them to product templates. This way, customers can be presented with unique products with zero effort of creating an actual design.&lt;/p&gt;
&lt;p&gt;Even without automatically generated custom products, an app-to-print editor provides a much smoother editing experience and interface, with native hardware acceleration, local storage, and easier access to photos or even a camera. Moreover, apps present the possibility to provide more accurate recommendations and personalized content, like in-app notifications, reminders, and activity-based discounts. Hence, developing a quality mobile app could not only boost sales, but also increase brand loyalty and customer satisfaction.&lt;/p&gt;
&lt;h2 id=&quot;streamlining-mobile-web-to-print-editing-with-cesdk&quot;&gt;Streamlining Mobile Web-to-Print Editing with CE.SDK&lt;/h2&gt;
&lt;p&gt;With the undeniable advantages of &lt;a href=&quot;https://img.ly/industries/print&quot;&gt;web-to-print design tools&lt;/a&gt; come significant challenges in their development. However, &lt;a href=&quot;https://img.ly/creative-sdk&quot;&gt;CreativeEditor SDK&lt;/a&gt; offers a powerful design editor that delivers consistent experiences across all platforms. Our cross-platform engine can power any editor UI, ensuring 100% consistency in the rendered designs across different devices.&lt;/p&gt;
&lt;p&gt;Discover how CE.SDK can streamline your mobile web-to-print process with the following capabilities:&lt;/p&gt;
&lt;h3 id=&quot;efficient-client-side-encoding&quot;&gt;Efficient Client-Side Encoding&lt;/h3&gt;
&lt;p&gt;Our client-side encoding ensures a smooth and fast design process, as the editor runs directly on the user’s mobile device or web browser. This not only reduces upload time and bandwidth costs, but also allows for easy scalability without incurring additional server costs.&lt;/p&gt;
&lt;h3 id=&quot;seamless-cross-platform-integration&quot;&gt;Seamless Cross-Platform Integration&lt;/h3&gt;
&lt;p&gt;CE.SDK simplifies the process of building an application for the web, iOS, Android, and server by providing a creative engine that’s portable across all platforms. This means that you can easily integrate our solution into your existing systems, saving valuable time and resources. And with consistent design and functionality across all devices, your users can switch between platforms with ease.&lt;/p&gt;
&lt;h3 id=&quot;powerful-editing-features&quot;&gt;Powerful Editing Features&lt;/h3&gt;
&lt;p&gt;With CE.SDK, your users can unleash their creativity and design high-quality prints effortlessly. Take advantage of our powerful editing features, including the ability to capture and edit photos. CE.SDK also allows you to predefine the design process with adjustable placeholders and constraints, making sure that your final product meets all of the necessary print criteria.&lt;/p&gt;
&lt;h3 id=&quot;preview--design-validation&quot;&gt;Preview &amp;#x26; Design Validation&lt;/h3&gt;
&lt;p&gt;Imagine being able to preview your design in real-time, ensuring your product expectations are exceeded every time. Plus, with automatic design validation, you can rest easy knowing that no element will extend beyond the bleed margins, minimizing the risk of misprints. Don’t just take our word for it, check out our &lt;a href=&quot;https://img.ly/showcases/cesdk/web/design-validation&quot;&gt;showcases&lt;/a&gt; and see for yourself how CE.SDK can transform your design process.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As we navigate the ever-changing landscape of technology, it’s important to stay ahead of the curve and keep up with emerging trends. With mobile devices becoming increasingly essential in our lives, it’s an crucial time to invest in creating a delightful mobile experience for your customers. By designing an intuitive website, prioritizing fast page load times, and providing a smooth checkout process, you can create a seamless experience for your users. And for print companies, taking it a step further with an exceptional in-app custom-print design tool is a must to help users bring their dream creations to life. These investments can not only enhance customer satisfaction but also give your business a competitive edge in the market.&lt;/p&gt;
&lt;p&gt;However, building a cross-platform editor from scratch can be a time-consuming and costly venture with numerous technical challenges. Fortunately, IMG.LY has developed a solution for those who want a quicker and more efficient option. Check out our &lt;a href=&quot;https://img.ly/creative-sdk&quot;&gt;CreativeEditor SDK&lt;/a&gt;, which enables seamless integration of a robust editor for &lt;a href=&quot;https://img.ly/industries/print&quot;&gt;web-to-print&lt;/a&gt; across desktop, mobile, and server.&lt;/p&gt;
&lt;p&gt;If you’re interested in integrating CE.SDK, our documentation provides detailed information on how to get started, or you can &lt;a href=&quot;https://img.ly/free-trial&quot;&gt;try it out for free&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading. To stay in the loop, subscribe to our &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;Newsletter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Klaudia</dc:creator><media:content url="https://blog.img.ly/2023/02/mobile-optimization-print-app-creative-editor-for-print-app.jpg" medium="image"/><category>Web-to-print</category><category>Print</category><category>CE.SDK</category><category>Creative Editor</category><category>Mobile App Development</category><category>Learning</category></item><item><title>How to Apply Filter Effects to Video using VidEffects library on Android</title><link>https://img.ly/blog/how-to-apply-filter-effects-to-video-using-videffects-on-android/</link><guid isPermaLink="true">https://img.ly/blog/how-to-apply-filter-effects-to-video-using-videffects-on-android/</guid><description>Learn about the benefits and limitations of VidEffects for video editing on Android.</description><pubDate>Tue, 24 May 2022 11:41:23 GMT</pubDate><content:encoded>&lt;p&gt;Video manipulation is a complex topic on Android because there is no out-of-the-box support from the Android SDK, and using FFmpeg requires not just a very elaborate setup but having to climb a steep learning curve to learn its CLI commands.&lt;/p&gt;
&lt;p&gt;As an aside, we have also put together how-to blogs to elaborate on other video manipulation operations like merging still images to form videos, &lt;a href=&quot;https://img.ly/blog/working-with-large-video-and-image-files-on-ios-with-swift/&quot;&gt;compressing and resizing videos&lt;/a&gt;, or background removal.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/krazykira/VidEffects&quot;&gt;VidEffects&lt;/a&gt; library is much faster to set up than FFmpeg, and it works by applying effects to &lt;code&gt;GLSurfaceView&lt;/code&gt;. This blog post will demonstrate how to apply Filter effects to a video in an Android app using the VidEffects library.&lt;/p&gt;
&lt;p&gt;VidEffects does support storing videos on the disk, but it has limitations in this regard which we will discuss in a moment.&lt;/p&gt;
&lt;p&gt;Before we begin, keep in mind that the VidEffects library differentiates between &lt;strong&gt;Effects&lt;/strong&gt; and &lt;strong&gt;Filters&lt;/strong&gt;. As an example, we will apply both and discuss the crucial difference between the two.&lt;/p&gt;
&lt;p&gt;Without much further ado, let’s start coding.&lt;/p&gt;
&lt;h2 id=&quot;create-a-videffects-example-app&quot;&gt;Create a VidEffects Example App&lt;/h2&gt;
&lt;p&gt;Create a &lt;strong&gt;New Project&lt;/strong&gt; with an Empty Activity in Android Studio. Select &lt;em&gt;Kotlin&lt;/em&gt; as the language (or Java if you wish), and set **Minimum SDK to API 21.&lt;/p&gt;
&lt;p&gt;You can also download the final code from &lt;a href=&quot;https://github.com/numerative/videffects_sample&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;add-dependency&quot;&gt;Add Dependency&lt;/h3&gt;
&lt;p&gt;In your module-level &lt;strong&gt;build.gradle&lt;/strong&gt; (app/build.gradle) file add the latest VidEffects dependency.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;groovy&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dependencies {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    implementation &lt;/span&gt;&lt;span&gt;&quot;com.github.krazykira:videffects:1.1.1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, enable &lt;code&gt;viewBinding&lt;/code&gt; by adding the following lines within the &lt;code&gt;android{}&lt;/code&gt; closure.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;groovy&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;android {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    buildFeatures {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        viewBinding &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By using View Binding, we can avoid the &lt;code&gt;findViewById()&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;Click &lt;strong&gt;Sync Now&lt;/strong&gt; to sync the project with gradle files.&lt;/p&gt;
&lt;h3 id=&quot;add-video-asset-file&quot;&gt;Add Video Asset File&lt;/h3&gt;
&lt;p&gt;Place the sample video file in &lt;strong&gt;app/src/main/assets/&lt;/strong&gt; directory. Ideally, your app will load the video file from disk – but to avoid complexity, we are placing the video in the assets directory.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;directory_tree_android_video-effect&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 304px) 304px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;304&quot; height=&quot;299&quot; src=&quot;https://img.ly/_astro/directory_tree_android_video-effect_4vaxk.webp&quot; srcset=&quot;/_astro/directory_tree_android_video-effect_4vaxk.webp 304w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;create-a-layout&quot;&gt;Create a Layout&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;screen_no_filter-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 500px) 500px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;500&quot; height=&quot;889&quot; src=&quot;https://img.ly/_astro/screen_no_filter-1_g8KRl.webp&quot; srcset=&quot;/_astro/screen_no_filter-1_g8KRl.webp 500w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Create the above layout with a &lt;code&gt;VideoSurfaceView&lt;/code&gt;, three Filter buttons, and one Reset Button.&lt;/p&gt;
&lt;p&gt;Replace your &lt;strong&gt;activity_main.xml&lt;/strong&gt;`s code with the following.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;xml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;androidx.constraintlayout.widget.ConstraintLayout&lt;/span&gt;&lt;span&gt; xmlns:android&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;http://schemas.android.com/apk/res/android&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    xmlns:app&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;http://schemas.android.com/apk/res-auto&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    xmlns:tools&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;http://schemas.android.com/tools&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;match_parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;match_parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    tools:context&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;.MainActivity&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;com.sherazkhilji.videffects.view.VideoSurfaceView&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/mVideoSurfaceView&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;match_parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;300dp&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintTop_toTopOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;androidx.constraintlayout.widget.ConstraintLayout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/wrapper&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintEnd_toEndOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintStart_toStartOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintTop_toBottomOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@id/mVideoSurfaceView&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;androidx.appcompat.widget.AppCompatButton&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/bwButton&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:text&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Black and White&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            app:layout_constraintStart_toStartOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            app:layout_constraintTop_toTopOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;androidx.appcompat.widget.AppCompatButton&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/grainButton&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:text&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Grain&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            app:layout_constraintStart_toEndOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@id/bwButton&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            app:layout_constraintTop_toTopOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;androidx.appcompat.widget.AppCompatButton&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/duotoneButton&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:text&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;DuoTone&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            app:layout_constraintStart_toEndOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@id/grainButton&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            app:layout_constraintTop_toTopOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;androidx.constraintlayout.widget.ConstraintLayout&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;androidx.appcompat.widget.AppCompatButton&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/resetButton&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:text&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Reset&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintEnd_toEndOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintStart_toStartOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintTop_toBottomOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/wrapper&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;androidx.constraintlayout.widget.ConstraintLayout&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;initialize-the-video&quot;&gt;Initialize the video&lt;/h2&gt;
&lt;p&gt;Before initializing the video, set up an instance of the Binding class to use it within the Activity.&lt;/p&gt;
&lt;h3 id=&quot;setup-view-binding&quot;&gt;Setup View Binding&lt;/h3&gt;
&lt;p&gt;Add the following code in &lt;strong&gt;MainActivity.kt&lt;/strong&gt; before calling &lt;code&gt;setContentView()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you do not wish to use View Binding and instead prefer using &lt;code&gt;findViewById()&lt;/code&gt;, skip to the next step &lt;em&gt;Initializing the Sample Video&lt;/em&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; lateinit&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt; binding: &lt;/span&gt;&lt;span&gt;ActivityMainBinding&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MainActivity&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;AppCompatActivity&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		private&lt;/span&gt;&lt;span&gt; lateinit&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt; binding: &lt;/span&gt;&lt;span&gt;ActivityMainBinding&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState: &lt;/span&gt;&lt;span&gt;Bundle&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        binding &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ActivityMainBinding.&lt;/span&gt;&lt;span&gt;inflate&lt;/span&gt;&lt;span&gt;(layoutInflater)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    companion&lt;/span&gt;&lt;span&gt; object&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        private&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; val&lt;/span&gt;&lt;span&gt; TAG &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;MainActivity&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;ActivityMainBinding&lt;/code&gt; class is auto-generated. If it did not get auto-generated, then sync the project with the gradle file from &lt;strong&gt;File &gt; Sync Project with Gradle Files.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Next, pass the &lt;em&gt;root view&lt;/em&gt; to the &lt;code&gt;setContentView()&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MainActivity&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;AppCompatActivity&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState: &lt;/span&gt;&lt;span&gt;Bundle&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        setContentView&lt;/span&gt;&lt;span&gt;(binding.root)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;initializing-the-sample-video&quot;&gt;Initializing the Sample Video&lt;/h3&gt;
&lt;p&gt;Next, declare and initialize the &lt;code&gt;MediaPlayer&lt;/code&gt; variable.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MainActivity&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;AppCompatActivity&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    private&lt;/span&gt;&lt;span&gt; lateinit&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt; mMediaPlayer: &lt;/span&gt;&lt;span&gt;MediaPlayer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState: &lt;/span&gt;&lt;span&gt;Bundle&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            mMediaPlayer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MediaPlayer&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then open the video file from our assets directory and pass it as the data source to the Media Player.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState: &lt;/span&gt;&lt;span&gt;Bundle&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        val&lt;/span&gt;&lt;span&gt; afd &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; assets.&lt;/span&gt;&lt;span&gt;openFd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;sample.mp4&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        mMediaPlayer.&lt;/span&gt;&lt;span&gt;setDataSource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            afd.fileDescriptor,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            afd.startOffset, afd.length&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (e: &lt;/span&gt;&lt;span&gt;Exception&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        Log.&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;(TAG, e.message, e)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, all that is remaining is to initialize the &lt;code&gt;VideoSurfaceView&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState: &lt;/span&gt;&lt;span&gt;Bundle&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        binding.mVideoSurfaceView.&lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt;(mMediaPlayer, &lt;/span&gt;&lt;span&gt;NoEffectFilter&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your app should run. While the buttons still do not work, we will &lt;code&gt;setOnClickListeners()&lt;/code&gt; next.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;screen_no_filter-2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 500px) 500px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;500&quot; height=&quot;889&quot; src=&quot;https://img.ly/_astro/screen_no_filter-2_biy8Y.webp&quot; srcset=&quot;/_astro/screen_no_filter-2_biy8Y.webp 500w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;apply-videffects-effects&quot;&gt;Apply VidEffects Effects&lt;/h2&gt;
&lt;p&gt;The VidEffects library offers &lt;a href=&quot;https://github.com/krazykira/VidEffects#supported-effects&quot;&gt;multiple effects&lt;/a&gt;, but we will be applying three: Black and White, Grain, and Duotone.&lt;/p&gt;
&lt;p&gt;In &lt;strong&gt;MainActivity&lt;/strong&gt;’s &lt;code&gt;onCreate()&lt;/code&gt;, copy the following code to setup all the &lt;code&gt;setOnClickListeners&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState: &lt;/span&gt;&lt;span&gt;Bundle&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //Black and White Effect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    binding.bwButton.&lt;/span&gt;&lt;span&gt;setOnClickListener&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        binding.mVideoSurfaceView.shader &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; BlackAndWhiteEffect&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //Grain Filter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    binding.grainButton.&lt;/span&gt;&lt;span&gt;setOnClickListener&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        val&lt;/span&gt;&lt;span&gt; grainFilter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; GrainFilter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        grainFilter.&lt;/span&gt;&lt;span&gt;setIntensity&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.5f&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        binding.mVideoSurfaceView.filter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; grainFilter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //Duotone Effect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    binding.duotoneButton.&lt;/span&gt;&lt;span&gt;setOnClickListener&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        binding.mVideoSurfaceView.shader &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            DuotoneEffect&lt;/span&gt;&lt;span&gt;(Color.BLUE, Color.YELLOW)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //Reset with AutoFixFilter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    binding.resetButton.&lt;/span&gt;&lt;span&gt;setOnClickListener&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        binding.mVideoSurfaceView.filter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; NoEffectFilter&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the app.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;screen-bw-duo-grain&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1500px) 1500px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1500&quot; height=&quot;889&quot; src=&quot;https://img.ly/_astro/screen-bw-duo-grain_U18c2.webp&quot; srcset=&quot;/_astro/screen-bw-duo-grain_6sBsy.webp 640w, /_astro/screen-bw-duo-grain_1q99jx.webp 750w, /_astro/screen-bw-duo-grain_Z1jzBKe.webp 828w, /_astro/screen-bw-duo-grain_ZowVa8.webp 1080w, /_astro/screen-bw-duo-grain_Z2lItwG.webp 1280w, /_astro/screen-bw-duo-grain_U18c2.webp 1500w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;difference-between-effects-and-filters&quot;&gt;Difference Between Effects and Filters&lt;/h2&gt;
&lt;p&gt;As mentioned before, Effects and Filters are two separate functions in the VidEffects library.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Effects&lt;/em&gt; are temporary, view-only overlays that cannot be stored on the disk. &lt;em&gt;Filters&lt;/em&gt; can be viewed and stored on the disk. Unfortunately, a majority of the overlays offered by VidEffects are &lt;a href=&quot;https://github.com/krazykira/VidEffects/tree/master/videffects/src/main/java/com/sherazkhilji/videffects&quot;&gt;Effects&lt;/a&gt; and therefore only useful for playback only. At the time of writing this article, only three &lt;a href=&quot;https://github.com/krazykira/VidEffects/tree/master/videffects/src/main/java/com/sherazkhilji/videffects/filter&quot;&gt;Filters&lt;/a&gt; are available.&lt;/p&gt;
&lt;h2 id=&quot;limitations&quot;&gt;Limitations&lt;/h2&gt;
&lt;p&gt;One of the biggest limitations of this library, as mentioned above, is that the majority of the effects provided by the library are just view-only.&lt;/p&gt;
&lt;p&gt;Moreover, to save videos with filters, the app must target &lt;code&gt;minSdk 23&lt;/code&gt; at least or use FFmpeg.&lt;/p&gt;
&lt;p&gt;Many of these Effects do not have an intensity parameter and therefore we cannot fine-tune these effects.&lt;/p&gt;
&lt;h2 id=&quot;alternative&quot;&gt;Alternative&lt;/h2&gt;
&lt;p&gt;If you are looking for an easy-to-integrate solution that overcomes these limitations, take a look at IMG.LY’s &lt;a href=&quot;https://img.ly/docs/vesdk/android/introduction/getting_started?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditor SDK&lt;/a&gt;. It provides more than 60 high-quality adjustable filters out-of-the-box. Moreover, you can easily add &lt;a href=&quot;https://img.ly/docs/vesdk/android/features/filters/#add-custom-lut-filters?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;custom filters&lt;/a&gt; using LUT files. VideoEditor SDK works for Android SDK version 21 and above and thus targets more devices.&lt;/p&gt;
&lt;p&gt;If the purpose is to apply effects during the playback, the VidEffects library is a solid and free open-source solution. While it handles all the complex video editing, developers will have to spend some time developing the layout.&lt;/p&gt;
&lt;h3 id=&quot;thanks-for-reading-let-us-know-what-you-think-on-twitter-or-stay-in-the-loop-with-our-newsletter&quot;&gt;Thanks for reading! Let us know what you think on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt;, or stay in the loop with our &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;Newsletter&lt;/a&gt;.&lt;/h3&gt;</content:encoded><dc:creator>Neslihan</dc:creator><media:content url="https://blog.img.ly/2022/05/videffects-add-filter-to-video-android.png" medium="image"/><category>How-To</category><category>Video Editing</category><category>Android</category><category>Android App Development</category><category>Mobile App Development</category><category>Tutorial</category></item><item><title>How To Add Overlays to a Video in React Native</title><link>https://img.ly/blog/how-to-add-overlays-to-a-video-in-react-native/</link><guid isPermaLink="true">https://img.ly/blog/how-to-add-overlays-to-a-video-in-react-native/</guid><description>Learn how to place text or images over your videos in React Native.</description><pubDate>Mon, 28 Mar 2022 16:23:32 GMT</pubDate><content:encoded>&lt;p&gt;In this article, you will learn to add a text and image overlay to a video with JavaScript in &lt;a href=&quot;https://reactnative.dev/&quot;&gt;React Native&lt;/a&gt;. All you need is &lt;a href=&quot;https://github.com/TheWidlarzGroup/react-native-video&quot;&gt;React Native Video&lt;/a&gt;, the correct styling rules, and just a few lines of code.&lt;/p&gt;
&lt;p&gt;By following this guide, you will easily achieve a logo and text overlay:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;qtNLErZ&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 188px) 188px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;188&quot; height=&quot;400&quot; src=&quot;https://img.ly/_astro/qtNLErZ_Z1IVhSU.webp&quot; srcset=&quot;/_astro/qtNLErZ_Z1IVhSU.webp 188w&quot;&gt;&lt;/p&gt;
&lt;p&gt;First, let’s learn how to implement a React Native component that allows you to add an overlay image and overlay text to a video.&lt;/p&gt;
&lt;h2 id=&quot;what-is-react-native-video&quot;&gt;What is React Native Video?&lt;/h2&gt;
&lt;p&gt;Mobile apps come with several native interfaces and features. This is what makes React Native different from React, which is based on browser rendering. So, &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt; becomes &lt;a href=&quot;https://reactnative.dev/docs/view&quot;&gt;&lt;code&gt;&amp;#x3C;View&gt;&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;&amp;#x3C;p&gt;&lt;/code&gt; becomes &lt;a href=&quot;https://reactnative.dev/docs/text&quot;&gt;&lt;code&gt;&amp;#x3C;Text&gt;&lt;/code&gt;&lt;/a&gt;, and so on. In particular, React Native supports only a limited amount of components. You can find the entire list &lt;a href=&quot;https://reactnative.dev/docs/components-and-apis&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To this day, React Native does not have a native &lt;code&gt;&amp;#x3C;Video&gt;&lt;/code&gt; component. That is why &lt;code&gt;react-native-video&lt;/code&gt; was born - a widely used library without considering alternatives. This 500kB package equips you with everything you need to get started with videos in React Native.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Here is the list of all the prerequisites for the demo application you are going to build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.npmjs.com/getting-started/&quot;&gt;Node.js and npm&lt;/a&gt; &lt;a href=&quot;https://docs.npmjs.com/getting-started/&quot;&gt;7+&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-native&quot;&gt;&lt;code&gt;react-native&lt;/code&gt;&lt;/a&gt; &gt;= 0.6x&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-native-video&quot;&gt;&lt;code&gt;react-native-video&lt;/code&gt;&lt;/a&gt; &gt;= 5.x&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can add &lt;code&gt;react-native-video&lt;/code&gt; to your project’s dependencies by launching the following npm command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm install react-native-video&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or if you are a yarn user:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;yarn add react-native-video&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, follow &lt;a href=&quot;https://github.com/TheWidlarzGroup/react-native-video#installation&quot;&gt;this&lt;/a&gt; guide to learn how to link &lt;code&gt;react-native-video&lt;/code&gt; to your project. This step is not required for Android users with React Native 0.60 and above.&lt;/p&gt;
&lt;h2 id=&quot;adding-overlay-image-and-text-to-a-video-in-react-native&quot;&gt;Adding Overlay Image and Text to a Video in React Native&lt;/h2&gt;
&lt;p&gt;You can clone the &lt;a href=&quot;https://github.com/Tonel/how-to-add-overlays-to-videos-in-react-native-imgly&quot;&gt;GitHub repository supporting this article&lt;/a&gt; and launch the following commands to try the demo application:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git clone https://github.com/Tonel/how-to-add-overlays-to-videos-in-react-native-imgly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cd how-to-add-overlays-to-videos-in-react-native-imgly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The project is a React Native Create App project, and you can learn more about how it works and how to run it &lt;a href=&quot;https://reactnative.dev/blog/2017/03/13/introducing-create-react-native-app&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Otherwise, keep following this step-by-step tutorial and learn how to build a React Native component for adding overlays to a video.&lt;/p&gt;
&lt;h3 id=&quot;1-initializing-a-react-native-project&quot;&gt;1. Initializing a React Native Project&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://reactnative.dev/blog/2017/03/13/introducing-create-react-native-app&quot;&gt;Create React Native App&lt;/a&gt; is the officially supported and easiest way to create an empty and working React Native application. Install it globally with the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm install -g create-react-native-app&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you can initialize a new project called &lt;code&gt;react-native-video-overlays-demo&lt;/code&gt; with the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;create-react-native-app react-native-video-overlays-demo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;react-native-video-overlays-demo&lt;/code&gt; folder should now contain a demo project with the following file structure:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;react-native-video-overlays-demo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── .buckconfig&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── .gitattributes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── .gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── App.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── app.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── babel.config.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── index.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── metro.config.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── package.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── package-lock.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── yarn.lock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── android&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   └── ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── ios&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    └── ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enter the &lt;code&gt;react-native-video-overlays-demo&lt;/code&gt; folder:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cd react-native-video-overlays-demo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then launch the development server with one of the following commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;for launching an &lt;a href=&quot;https://expo.dev/&quot;&gt;Expo&lt;/a&gt; app&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;for Android development on your phone or an emulator&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm run android&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;for iOS development on your phone or an emulator&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm run ios&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;for &lt;a href=&quot;https://github.com/necolas/react-native-web&quot;&gt;&lt;code&gt;react-native-web&lt;/code&gt;&lt;/a&gt; development&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm run web&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-defining-a-component-to-add-overlays-on-a-video&quot;&gt;2. Defining a Component to Add Overlays on a Video&lt;/h3&gt;
&lt;p&gt;Let’s see how to build a &lt;code&gt;VideoWithOverlays&lt;/code&gt; component that allows you to add overlays such as text and an image to a video. You can implement it as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; React, { useEffect, useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { Image, StyleSheet, Text, View } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react-native&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Video &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react-native-video&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; VideoWithOverlays&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  videoComponentProps&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  text&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  imageSrc&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    video&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    overlayText&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    videoWithOverlays&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    overlayTextView&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    overlayImageView&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    overlayImage&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; styles;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setHeight&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;overlayHeight&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setOverlayHeight&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // setting the overlay height to 10px starting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // from the bottom of the video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    setOverlayHeight&lt;/span&gt;&lt;span&gt;(height &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; height &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 3&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }, [height]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{videoWithOverlays}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;Video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        {&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;videoComponentProps}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{video}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        onLayout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; event.nativeEvent.layout;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          setHeight&lt;/span&gt;&lt;span&gt;(height);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;overlayTextView, top: overlayHeight }}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        {text &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; &amp;#x3C;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{overlayText}&gt;{text}&amp;#x3C;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;overlayImageView, top: overlayHeight }}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        {imageSrc &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;&lt;/span&gt;&lt;span&gt;Image&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{overlayImage} &lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{imageSrc} &lt;/span&gt;&lt;span&gt;resizeMode&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;contain&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        )}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; styles&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; StyleSheet.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  overlayTextView: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    position: &lt;/span&gt;&lt;span&gt;&apos;relative&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    alignItems: &lt;/span&gt;&lt;span&gt;&apos;flex-end&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    justifyContent: &lt;/span&gt;&lt;span&gt;&apos;flex-end&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    right: &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  overlayImageView: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    position: &lt;/span&gt;&lt;span&gt;&apos;relative&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    alignItems: &lt;/span&gt;&lt;span&gt;&apos;flex-start&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    justifyContent: &lt;/span&gt;&lt;span&gt;&apos;flex-end&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    left: &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  overlayImage: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    position: &lt;/span&gt;&lt;span&gt;&apos;absolute&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    height: &lt;/span&gt;&lt;span&gt;40&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  overlayText: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    fontSize: &lt;/span&gt;&lt;span&gt;25&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    fontWeight: &lt;/span&gt;&lt;span&gt;&apos;bold&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    color: &lt;/span&gt;&lt;span&gt;&apos;white&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  video: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    position: &lt;/span&gt;&lt;span&gt;&apos;absolute&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    top: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    left: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    bottom: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    right: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    marginTop: &lt;/span&gt;&lt;span&gt;&apos;50%&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  videoWithOverlays: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    position: &lt;/span&gt;&lt;span&gt;&apos;absolute&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    top: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    left: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    bottom: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    right: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;react-native-video&lt;/code&gt; &lt;code&gt;Video&lt;/code&gt; component is used to play the video and its props are exposed thanks to the &lt;code&gt;videoComponentProps&lt;/code&gt; prop. Then, after being placed into the layout, its height depending on the size of the device screen is stored in the &lt;code&gt;height&lt;/code&gt; variable. This is used to calculate the height the overlays should be placed at in the &lt;a href=&quot;https://legacy.reactjs.org/docs/hooks-effect.html&quot;&gt;&lt;code&gt;useEffect&lt;/code&gt;&lt;/a&gt; hook. In detail, the overlays are placed at 10px from the bottom edge of the video.&lt;/p&gt;
&lt;p&gt;The first internal &lt;code&gt;View&lt;/code&gt; represents the optional text overlay. The overlay logic depends entirely on the custom &lt;code&gt;overlayTextView&lt;/code&gt; and &lt;code&gt;overlayText&lt;/code&gt; &lt;a href=&quot;https://reactnative.dev/docs/stylesheet&quot;&gt;StyleSheet&lt;/a&gt; fields, representing the CSS classes responsible for displaying the text passed as a prop with &lt;code&gt;text&lt;/code&gt; as an overlay. By default, the text overlay is placed in the bottom-right corner of the video.&lt;/p&gt;
&lt;p&gt;The second internal &lt;code&gt;View&lt;/code&gt; represents the optional image overlay. The overlay logic depends entirely on the custom &lt;code&gt;overlayImageView&lt;/code&gt; and &lt;code&gt;overlayImage&lt;/code&gt; &lt;a href=&quot;https://reactnative.dev/docs/stylesheet&quot;&gt;StyleSheet&lt;/a&gt; fields, representing the CSS classes taking care of showing the image passed as a prop with &lt;code&gt;imageSrc&lt;/code&gt; as an overlay. By default, the image overlay is placed in the bottom-left corner of the video. As you can see, both overlays are optional.&lt;/p&gt;
&lt;h3 id=&quot;3-putting-it-all-together&quot;&gt;3. Putting It All Together&lt;/h3&gt;
&lt;p&gt;Let’s see the &lt;code&gt;VideoWithOverlays&lt;/code&gt; component defined above in action. All you need to do is replace the &lt;code&gt;App.js&lt;/code&gt; file with this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; React &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { StyleSheet, View } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react-native&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; VideoWithOverlays &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./src/components/VideoWithOverlays/VideoWithOverlays&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; App&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // a static image stored into the assets folder&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; imgLy&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;./src/assets/images/IMG_LY.png&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{styles.container}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;VideoWithOverlays&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        videoComponentProps&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          // replace this free video source with your video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          source: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            uri: &lt;/span&gt;&lt;span&gt;&apos;https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // overlay text&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        text&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&apos;IMG.LY&apos;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // overlay image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        imageSrc&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{imgLy}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; styles&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; StyleSheet.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  container: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    flex: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    backgroundColor: &lt;/span&gt;&lt;span&gt;&apos;#fff&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    alignItems: &lt;/span&gt;&lt;span&gt;&apos;center&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    justifyContent: &lt;/span&gt;&lt;span&gt;&apos;center&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;&amp;#x3C;VideoWitOverlays&gt;&lt;/code&gt; is initialized with a free video source retrieved from a public URL and two overlays. The text overlay states “IMG.LY” and it is placed in the bottom-right corner of the video. While the image overlay represents the IMG.LY logo and is placed in the bottom-left corner of the video. This is a screenshot of the &lt;code&gt;VideoWitOverlays&lt;/code&gt; in action:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;react-native-video-add-text-image&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 189px) 189px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;189&quot; height=&quot;400&quot; src=&quot;https://img.ly/_astro/react-native-video-add-text-image_Z1GJNu7.webp&quot; srcset=&quot;/_astro/react-native-video-add-text-image_Z1GJNu7.webp 189w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;final-considerations&quot;&gt;Final Considerations&lt;/h2&gt;
&lt;p&gt;Adding overlays elements to a video in React Native is not complex and takes just a few lines of code. On the other hand, the approach presented above is just a frontend trick and does not modify the video. You are just showing it behind text or an image, giving the illusion to the users that it is a single source of content.&lt;/p&gt;
&lt;p&gt;If you wanted to let users modify their videos by adding overlays or other elements and then export them, this would involve complex logic and require an advanced UI. Building such a React Native component is a time-consuming task that represents a waste of energy. If that is your goal, you should take into consideration a complete and ready-to-use SDK solution, such as &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;VideoEditorSDK&lt;/a&gt;. This would provide your users with the ability to add overlays and use many other cool features.&lt;/p&gt;
&lt;h2 id=&quot;add-overlays-to-a-video-with-react-native-module-for-videoeditor-sdk&quot;&gt;Add Overlays to a Video With &lt;a href=&quot;https://www.npmjs.com/package/react-native-videoeditorsdk&quot;&gt;React Native module for VideoEditor SDK&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href=&quot;https://img.ly/docs/pesdk/react-native/getting-started/integration/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;this&lt;/a&gt; page from the official documentation to learn how to get started with VideoEditorSDK in React Native. Then, load your video and start editing it by adding &lt;a href=&quot;https://img.ly/docs/vesdk/android/guides/overlays/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;overlays&lt;/a&gt;, &lt;a href=&quot;https://img.ly/docs/vesdk/android/guides/text-fonts/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;text&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/docs/vesdk/android/guides/stickers/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;stickers&lt;/a&gt;. Follow the previous links to learn more about each feature.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;add-image-text-to-video-react-native&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 333px) 333px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;333&quot; height=&quot;685&quot; src=&quot;https://img.ly/_astro/add-image-text-to-video-react-native_1Mw6kk.webp&quot; srcset=&quot;/_astro/add-image-text-to-video-react-native_1Mw6kk.webp 333w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Download our free mobile demo app from the &lt;a href=&quot;https://apps.apple.com/us/app/img-ly-photo-video-editor/id589839231&quot;&gt;App Store&lt;/a&gt; or &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.photoeditorsdk.android.app&quot;&gt;Google Play&lt;/a&gt;, and try an extensive set of tools to edit videos.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this tutorial, we learned how to define a React Native component that allows you to reproduce a video and add overlays. Specifically, we used &lt;code&gt;react-native-video&lt;/code&gt; and defined everything required to place text or an image on your video. However, if you intend to export your edit, you will have to build a complex video editing application. In this scenario, you will want to consider a fully-featured, cutting-edge, and easy-to-adopt solution – such as &lt;a href=&quot;https://img.ly/products/video-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditorSDK&lt;/a&gt;.&lt;br&gt;
Looking for more video capabilities? Check out our solution for &lt;a href=&quot;https://img.ly/use-cases/story-reels-short-video-creation&quot;&gt;Short Video Creation&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/products/camera-sdk&quot;&gt;Camera SDK&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Thanks for reading! We hope that you found this article helpful. Feel free to reach out to us on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt; with any questions, comments, or suggestions.&lt;/p&gt;</content:encoded><dc:creator>Antonello</dc:creator><media:content url="https://blog.img.ly/2022/03/add-image-text-to-video-react-native-mobile-3.png" medium="image"/><category>How-To</category><category>Image Editing</category><category>Video Editing</category><category>React Native</category><category>React</category><category>Mobile App Development</category><category>Tech</category><category>Tutorial</category></item><item><title>How To Resize and Compress an Image in JavaScript for Upload</title><link>https://img.ly/blog/how-to-compress-an-image-before-uploading-it-in-javascript/</link><guid isPermaLink="true">https://img.ly/blog/how-to-compress-an-image-before-uploading-it-in-javascript/</guid><description>Learn how to downscale an image in JavaScript by reducing its size and quality before uploading it to your server.</description><pubDate>Fri, 04 Mar 2022 16:13:56 GMT</pubDate><content:encoded>&lt;p&gt;In this article, you will learn to compress an image in JavaScript and then upload it to &lt;a href=&quot;https://imgur.com/&quot;&gt;Imgur&lt;/a&gt;. Similar to our previous guide on resizing images, and the one on drawing on images, you can accomplish everything with the HTML5 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API&quot;&gt;&lt;code&gt;&amp;#x3C;canvas&gt;&lt;/code&gt;&lt;/a&gt; element and we will involve any external libraries for this.&lt;/p&gt;
&lt;p&gt;Smartphones cameras have become increasingly accurate and enhanced their photo quality for years. Consequently, their file size has grown as well. Since the speed of the average network has not improved at the same pace, it is essential to compress the images before uploading them.&lt;/p&gt;
&lt;p&gt;Compressing is about downscaling an image. In other words, you want to reduce either its size or quality or both. By doing so, you can avoid uploading large images, saving the end-user time and money.&lt;/p&gt;
&lt;h2 id=&quot;compressing-an-image-with-canvas&quot;&gt;Compressing an Image With &lt;code&gt;&amp;#x3C;canvas&gt;&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;You can clone the &lt;a href=&quot;https://github.com/Tonel/how-to-compress-an-image-in-javascript-imgly&quot;&gt;GitHub repository that supports this article&lt;/a&gt; with the following commands:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git clone https://github.com/Tonel/how-to-compress-an-image-in-javascript-imgly&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, you can try the demo application by opening the &lt;code&gt;index.html&lt;/code&gt; file in your browser.&lt;/p&gt;
&lt;p&gt;Otherwise, keep following this step-by-step tutorial and learn how to build the demo application.&lt;/p&gt;
&lt;h3 id=&quot;1-implementing-the-compression-logic&quot;&gt;1. Implementing the Compression Logic&lt;/h3&gt;
&lt;p&gt;You can compress an image by solely using the HTML &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API&quot;&gt;&lt;code&gt;&amp;#x3C;canvas&gt;&lt;/code&gt;&lt;/a&gt; element. This is a powerful image manipulation tool that allows you to achieve many results, as we have already explored &lt;a href=&quot;https://img.ly/blog/tag/javascript/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;in our blog&lt;/a&gt;.&lt;br&gt;
Now, let’s delve into how to compress an image with &lt;code&gt;canvas&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; compressImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;imgToCompress&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;resizingFactor&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;quality&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // resizing the image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; canvas&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;createElement&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;canvas&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; canvas.&lt;/span&gt;&lt;span&gt;getContext&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;2d&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; originalWidth&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; imgToCompress.width;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; originalHeight&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; imgToCompress.height;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; canvasWidth&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; originalWidth &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; resizingFactor;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; canvasHeight&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; originalHeight &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; resizingFactor;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  canvas.width &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; canvasWidth;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  canvas.height &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; canvasHeight;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  context.&lt;/span&gt;&lt;span&gt;drawImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    imgToCompress,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    originalWidth &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; resizingFactor,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    originalHeight &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; resizingFactor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // reducing the quality of the image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  canvas.&lt;/span&gt;&lt;span&gt;toBlob&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;blob&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (blob) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // showing the compressed image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        resizedImage.src &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createObjectURL&lt;/span&gt;&lt;span&gt;(resizedImageBlob);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &apos;image/jpeg&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    quality&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function is an extension of the &lt;code&gt;resizeImage()&lt;/code&gt; function defined in &lt;a href=&quot;https://img.ly/blog/how-to-resize-an-image-with-javascript/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;this&lt;/a&gt; article. So, follow the link to that tutorial to learn more about it.&lt;/p&gt;
&lt;p&gt;What is new here are the last few lines, which take care of reducing the quality of the uploaded image based on the &lt;code&gt;quality&lt;/code&gt; parameter. As you can see, the last part of the &lt;code&gt;compressImage()&lt;/code&gt;is based on the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob&quot;&gt;&lt;code&gt;toBlob()&lt;/code&gt;&lt;/a&gt; function. This transforms the image stored in the canvas into a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Blob&quot;&gt;&lt;code&gt;Blob&lt;/code&gt;&lt;/a&gt; object and compresses it based on the last parameter passed to the function. This last parameter represents the quality of the target image file.&lt;/p&gt;
&lt;p&gt;Just like &lt;code&gt;resizingFactor&lt;/code&gt;, &lt;code&gt;quality&lt;/code&gt; must contain a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number&quot;&gt;&lt;code&gt;Number&lt;/code&gt;&lt;/a&gt; between 0 and 1.&lt;/p&gt;
&lt;p&gt;Also, since the &lt;code&gt;toBlob()&lt;/code&gt; function returns a &lt;code&gt;Blob&lt;/code&gt; object, you can store it in a global variable. Then, you can use the &lt;code&gt;blob&lt;/code&gt; object representing the compressed image file to upload it to your server. Let’s see how.&lt;/p&gt;
&lt;h3 id=&quot;2-uploading-an-image-to-imgur&quot;&gt;2. Uploading an Image to Imgur&lt;/h3&gt;
&lt;p&gt;First, you need an Imgur account. If you already have one, log in &lt;a href=&quot;https://imgur.com/signin&quot;&gt;here&lt;/a&gt;. Otherwise, create a new account for free &lt;a href=&quot;https://imgur.com/register&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, register an application &lt;a href=&quot;https://imgur.com/signin?redirect=https%3A%2F%2Fapi.imgur.com%2Foauth2%2Faddclient&quot;&gt;here&lt;/a&gt; to have access to the &lt;a href=&quot;https://apidocs.imgur.com/&quot;&gt;Imgur API program&lt;/a&gt;. Fill out the form as follows:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Registering an application on Imgur&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 749px) 749px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;749&quot; height=&quot;926&quot; src=&quot;https://img.ly/_astro/n118lrk_Z23SXGl.webp&quot; srcset=&quot;/_astro/n118lrk_ZpISqw.webp 640w, /_astro/n118lrk_Z23SXGl.webp 749w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then, click on “Submit” and you should get access to this page.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Client-ID Imgur page&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 732px) 732px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;732&quot; height=&quot;392&quot; src=&quot;https://img.ly/_astro/6wGxX79_1YAY5d.webp&quot; srcset=&quot;/_astro/6wGxX79_7amGE.webp 640w, /_astro/6wGxX79_1YAY5d.webp 732w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Store your &lt;code&gt;Client-ID&lt;/code&gt; in a safe place. You will need it later.&lt;br&gt;
Now, you have everything required to start uploading images to your Imgur application.&lt;/p&gt;
&lt;p&gt;Uploading an image to Imgur in JavaScript is easy and can be achieved with just a bunch of lines of code, as below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// compressedImageBlob represents the compressed image Blob to upload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; formdata&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; FormData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;formdata.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;image&apos;&lt;/span&gt;&lt;span&gt;, compressedImageBlob);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;https://api.imgur.com/3/image/&apos;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  method: &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Accept: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Authorization: &lt;/span&gt;&lt;span&gt;&apos;Client-ID YOUR_CLIENT_ID&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  body: formdata,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (response?.status &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; 403&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Unvalid Client-ID!&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (response?.status &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; 200&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // retrieving the URL of the image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // just uploaded to Imgur&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;jsonResponse&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`URL: ${&lt;/span&gt;&lt;span&gt;jsonResponse&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(response);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;YOUR_CLIENT_ID&lt;/code&gt; with the &lt;code&gt;Client-ID&lt;/code&gt; retrieved before, and you should now be able to use this snippet to upload your images to Imgur.&lt;/p&gt;
&lt;h3 id=&quot;3-putting-it-all-together&quot;&gt;3. Putting It All Together&lt;/h3&gt;
&lt;p&gt;Now it is time to see the &lt;code&gt;compressImage()&lt;/code&gt; function in action through a simple example.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;!&lt;/span&gt;&lt;span&gt;DOCTYPE&lt;/span&gt;&lt;span&gt; html&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &amp;#x3C;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&gt;Compress and Resize an Image&amp;#x3C;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;Upload an image and compress it or use the following demo image&amp;#x3C;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;upload&quot;&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;file&quot;&lt;/span&gt;&lt;span&gt; accept&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;image/*&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&gt;Original Image&amp;#x3C;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;margin-top: 5px;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;originalImage&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;demo.jpg&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        crossorigin&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;anonymous&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;margin-top: 5px;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&gt;Resizing: &amp;#x3C;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;range&quot;&lt;/span&gt;&lt;span&gt; min&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1&quot;&lt;/span&gt;&lt;span&gt; max&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;100&quot;&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;80&quot;&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;resizingRange&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;margin-top: 5px; margin-left: 8px;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&gt;Quality: &amp;#x3C;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;range&quot;&lt;/span&gt;&lt;span&gt; min&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1&quot;&lt;/span&gt;&lt;span&gt; max&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;100&quot;&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;80&quot;&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;qualityRange&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&gt;Compressed Image&amp;#x3C;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;&gt;Size:&amp;#x3C;/&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;&gt; &amp;#x3C;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;size&quot;&lt;/span&gt;&lt;span&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;compressedImage&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;uploadButton&quot;&lt;/span&gt;&lt;span&gt;&gt;Upload to Imgur&amp;#x3C;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;src/index.js&quot;&lt;/span&gt;&lt;span&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; fileInput&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;#upload&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; originalImage&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;#originalImage&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; compressedImage&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;#compressedImage&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; resizingElement&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;#resizingRange&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; qualityElement&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;#qualityRange&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; uploadButton&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;#uploadButton&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; compressedImageBlob;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; resizingFactor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0.8&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; quality &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0.8&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// initializing the compressed image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;compressImage&lt;/span&gt;&lt;span&gt;(originalImage, resizingFactor, quality);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fileInput.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;change&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; fileInput.files;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // storing the original image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  originalImage.src &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fileToDataUri&lt;/span&gt;&lt;span&gt;(file);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // compressing the uplodaded image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  originalImage.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;load&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    compressImage&lt;/span&gt;&lt;span&gt;(originalImage, resizingFactor, quality);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;resizingElement.&lt;/span&gt;&lt;span&gt;oninput&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  resizingFactor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(e.target.value) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 100&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  compressImage&lt;/span&gt;&lt;span&gt;(originalImage, resizingFactor, quality);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qualityElement.&lt;/span&gt;&lt;span&gt;oninput&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  quality &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(e.target.value) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 100&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  compressImage&lt;/span&gt;&lt;span&gt;(originalImage, resizingFactor, quality);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;uploadButton.&lt;/span&gt;&lt;span&gt;onclick&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // uploading the compressed image to&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Imgur (if present)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (compressedImageBlob) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; formdata&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; FormData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    formdata.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;image&apos;&lt;/span&gt;&lt;span&gt;, compressedImageBlob);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;https://api.imgur.com/3/image/&apos;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      method: &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        Accept: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        Authorization: &lt;/span&gt;&lt;span&gt;&apos;Client-ID YOUR_CLIENT_ID&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      body: formdata,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (response?.status &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; 403&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Unvalid Client-ID!&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (response?.status &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; 200&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // retrieving the URL of the image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // just uploaded to Imgur&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;jsonResponse&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`URL: ${&lt;/span&gt;&lt;span&gt;jsonResponse&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Upload completed succesfully!&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(response);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Rezind and compressed image missing!&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; compressImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;imgToCompress&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;resizingFactor&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;quality&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // showing the compressed image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; canvas&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;createElement&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;canvas&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; canvas.&lt;/span&gt;&lt;span&gt;getContext&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;2d&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; originalWidth&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; imgToCompress.width;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; originalHeight&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; imgToCompress.height;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; canvasWidth&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; originalWidth &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; resizingFactor;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; canvasHeight&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; originalHeight &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; resizingFactor;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  canvas.width &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; canvasWidth;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  canvas.height &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; canvasHeight;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  context.&lt;/span&gt;&lt;span&gt;drawImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    imgToCompress,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    originalWidth &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; resizingFactor,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    originalHeight &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; resizingFactor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // reducing the quality of the image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  canvas.&lt;/span&gt;&lt;span&gt;toBlob&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;blob&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (blob) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        compressedImageBlob &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; blob;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        compressedImage.src &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createObjectURL&lt;/span&gt;&lt;span&gt;(compressedImageBlob);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;#size&apos;&lt;/span&gt;&lt;span&gt;).innerHTML &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; bytesToSize&lt;/span&gt;&lt;span&gt;(blob.size);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &apos;image/jpeg&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    quality&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; fileToDataUri&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; reader&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; FileReader&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    reader.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;load&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      resolve&lt;/span&gt;&lt;span&gt;(reader.result);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    reader.&lt;/span&gt;&lt;span&gt;readAsDataURL&lt;/span&gt;&lt;span&gt;(field);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// source: https://stackoverflow.com/a/18650828&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; bytesToSize&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  var&lt;/span&gt;&lt;span&gt; sizes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;Bytes&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;KB&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;MB&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;GB&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;TB&apos;&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (bytes &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &apos;0 Byte&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(Math.&lt;/span&gt;&lt;span&gt;floor&lt;/span&gt;&lt;span&gt;(Math.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(bytes) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1024&lt;/span&gt;&lt;span&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;round&lt;/span&gt;&lt;span&gt;(bytes &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;pow&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1024&lt;/span&gt;&lt;span&gt;, i), &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &apos; &apos;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; sizes[i];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;input&lt;/code&gt; element allows users to upload an image. This is then passed to the &lt;code&gt;compressImage()&lt;/code&gt; function along with the &lt;code&gt;resizingFactor&lt;/code&gt; and &lt;code&gt;quality&lt;/code&gt; values retrieved from the respective &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range&quot;&gt;range &lt;code&gt;input&lt;/code&gt;&lt;/a&gt; HTML elements. This function takes care of compressing the image, displaying it, and storing its &lt;code&gt;Blob&lt;/code&gt; representation to the global &lt;code&gt;compressedImageBlob&lt;/code&gt; variable. Finally, &lt;code&gt;compressedImageBlob&lt;/code&gt; is uploaded to Imgur when the “Upload to Imgur” button is clicked.&lt;br&gt;
Notice that these two snippets are what allow you to implement the live example you can find at the beginning of the article.&lt;/p&gt;
&lt;h2 id=&quot;final-considerations&quot;&gt;Final Considerations&lt;/h2&gt;
&lt;p&gt;Compressing an image in Vanilla JavaScript is easy. You can achieve this with no extra libraries and in a dozen of lines of code. At the same time, the resizing part of the process relies on an &lt;a href=&quot;https://entropymine.com/resamplescope/notes/browsers/&quot;&gt;image interpolation algorithm&lt;/a&gt; that changes according to the browser in use. This can lead to different results based on the end-user’s browser. Also, compressing while preserving quality is always tricky and can easily become a grueling goal to achieve.&lt;br&gt;
If you want to avoid this stress, consider adopting a commercial and browser-consistent solution like &lt;a href=&quot;https://img.ly/products/photo-sdk&quot;&gt;PhotoEditorSDK&lt;/a&gt;. This library provides access to a wide set of tools that allow you to manipulate your images in many ways and with an advanced and easy-to-use UI.&lt;/p&gt;
&lt;h2 id=&quot;resizing-an-image-with-photoeditor-sdk&quot;&gt;Resizing an Image with PhotoEditor SDK&lt;/h2&gt;
&lt;p&gt;First, read &lt;a href=&quot;https://img.ly/docs/pesdk/web/guides/umd/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;this&lt;/a&gt; article from &lt;a href=&quot;https://img.ly/docs/pesdk/guides/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;the official documentation&lt;/a&gt; to get started with &lt;code&gt;PhotoEditorSDK&lt;/code&gt; in HTML and JavaScript. Then, you can use the &lt;a href=&quot;https://img.ly/docs/pesdk/web/features/transform/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;transform tool&lt;/a&gt; to &lt;a href=&quot;https://img.ly/docs/pesdk/web/features/transform/#image-resizing/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;resize&lt;/a&gt; your image, as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;resize-compress-javascript&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1206px) 1206px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1206&quot; height=&quot;647&quot; src=&quot;https://img.ly/_astro/resize-compress-javascript_Z1qzgpW.webp&quot; srcset=&quot;/_astro/resize-compress-javascript_ZkEPQy.webp 640w, /_astro/resize-compress-javascript_A4gEv.webp 750w, /_astro/resize-compress-javascript_1bihsS.webp 828w, /_astro/resize-compress-javascript_NDXgO.webp 1080w, /_astro/resize-compress-javascript_Z1qzgpW.webp 1206w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Check out this feature on the &lt;a href=&quot;https://img.ly/products/photo-sdk/demo?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;PhotoEditorSDK demo page&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this article, we learned how to downscale an image in JavaScript before uploading it to your server. In detail, we resized and reduced the quality of an uploaded image before uploading it to Imgur. Everything was achieved by using only the HTML5 &lt;code&gt;&amp;#x3C;canvas&gt;&lt;/code&gt; element. This is a powerful tool supported by most browsers that allows you to resize and change the quality of an image with just a few lines of code. On the other hand, this process may not be browser-consistent. That is why we also introduced a commercial and more reliable solution – such as &lt;a href=&quot;https://img.ly/products/photo-sdk&quot;&gt;PhotoEditorSDK&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for reading! We hope that you found this article helpful. Feel free to reach out to us on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt; with any questions, comments, or suggestions. To stay in the loop with our latest articles and case studies, subscribe to our &lt;a href=&quot;https://img.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;Newsletter&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Antonello</dc:creator><media:content url="https://blog.img.ly/2022/03/compress-resize-images-javascript-2.png" medium="image"/><category>How-To</category><category>JavaScript</category><category>Photo Editing</category><category>Web Development</category><category>Mobile App Development</category><category>Tech</category><category>Tutorial</category></item><item><title>How to Make Videos from Still Images with AVFoundation and Swift</title><link>https://img.ly/blog/how-to-make-videos-from-still-images-with-avfoundation-and-swift/</link><guid isPermaLink="true">https://img.ly/blog/how-to-make-videos-from-still-images-with-avfoundation-and-swift/</guid><description>Convert still images into video files with two strategies.</description><pubDate>Thu, 10 Feb 2022 15:35:45 GMT</pubDate><content:encoded>&lt;p&gt;Whether making a slide show or breaking up video tracks with title scenes, inserting still images into a video file can be tricky. This tutorial will show you how to convert still images into standard Quicktime video files so you can work with them further in your favorite video editor. This tutorial was created and tested with Xcode 13 and Swift 5. This tutorial will discuss two strategies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;convert each image to a separate movie and stitch them together later (this is best suited for times you want to interleave the images and other video files using some other editing workflow)&lt;/li&gt;
&lt;li&gt;create a “blank” video and use the &lt;code&gt;applyingCIFiltersWithHandler&lt;/code&gt; variation to overlay the images (this is best suited for times you want to make a quick slide show with no further editing)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As with most &lt;code&gt;AVFoundation&lt;/code&gt; code, the Xcode simulator is not always the best platform for running the code. Test on an actual device if you can. A demo project with this code (and some extra goodies) can be found on &lt;a href=&quot;https://github.com/waltertyree/static-image-slideshow&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-problem-with-an-image&quot;&gt;The Problem with an Image&lt;/h2&gt;
&lt;p&gt;The basic models that are used in &lt;code&gt;AVFoundation&lt;/code&gt; for making video files are &lt;code&gt;AVAsset&lt;/code&gt; and &lt;code&gt;AVAssetTrack&lt;/code&gt;. You can compose multiple tracks and assets together to make a single video file. All tracks must have some media data and a duration. Static image files don’t have a duration. This is the primary problem you have to overcome when you want to add static images to video.&lt;br&gt;
Fortunately, you can use many of the same &lt;code&gt;AVFoundation&lt;/code&gt; tools that are used for video capture and frame manipulation. However instead of using a &lt;code&gt;CADisplayLink&lt;/code&gt; and &lt;code&gt;AVPlayerItemVideoOutput&lt;/code&gt; or an &lt;code&gt;AVCaptureDevice&lt;/code&gt; to provide frame buffers to save, you will create the frame buffers manually. Then you can use &lt;code&gt;AVAssetWriter&lt;/code&gt; to convert the frame buffers into a video file.&lt;/p&gt;
&lt;p&gt;Some items to consider before you actually generate the video file are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What framerate to use? Because the image doesn’t have any motion, you can use a very low frame rate to create smaller file sizes. However, this might cause issues if you are interleaving it with regular video, which usually has a frame rate of 30 or 60 fps (or higher, for slow motion video)&lt;/li&gt;
&lt;li&gt;What dimensions will the final video have? It is easy to resize an image and add padding using &lt;code&gt;CoreImage&lt;/code&gt; or some image editing software before it becomes a video. As the image becomes a video and moves through different steps in an &lt;code&gt;AVFoundation&lt;/code&gt; pipeline, the different classes in &lt;code&gt;AVFoundation&lt;/code&gt; will resize or crop the images in different ways when the input dimensions and output dimension don’t match.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;writing-buffers-using-avassetwriter&quot;&gt;Writing Buffers using &lt;code&gt;AVAssetWriter&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;AVAssetWriter&lt;/code&gt; exists to encode media to a file on disk. Though this tutorial will use a single video input &lt;code&gt;AVAssetWriter&lt;/code&gt; supports multiple inputs of different kinds (audio, video, metadata). It is quite similar to &lt;code&gt;AVAssetExportSession&lt;/code&gt;. The difference is that &lt;code&gt;AVAssetWriter&lt;/code&gt; uses multiple inputs and each input is a single track where the &lt;code&gt;AVAssetExportSession&lt;/code&gt; takes a single &lt;code&gt;AVAsset&lt;/code&gt; as an input. The result of both is a single file.&lt;/p&gt;
&lt;p&gt;As indicated above, our basic strategy will be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a pixel buffer that is the correct color space and size to hold the image&lt;/li&gt;
&lt;li&gt;Render the image into the pixel buffer&lt;/li&gt;
&lt;li&gt;Create an &lt;code&gt;AVAssetWriter&lt;/code&gt; with a single video input&lt;/li&gt;
&lt;li&gt;Decide how many frames will be required to create a video of the desired duration&lt;/li&gt;
&lt;li&gt;Make a loop, and each time through the loop, append the pixel buffer&lt;/li&gt;
&lt;li&gt;Clean up&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;create-the-pixel-buffer&quot;&gt;Create the Pixel Buffer&lt;/h3&gt;
&lt;p&gt;The code below creates a &lt;code&gt;CIImage&lt;/code&gt; from an image file. Then it creates a pixel buffer the same size as the image and uses a standard color space. Finally, a &lt;code&gt;CIContext&lt;/code&gt; renders the image into the pixel buffer.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create a CIImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; uikitImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;named&lt;/span&gt;&lt;span&gt;: imageName), &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; staticImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt;: uikitImage) &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  throw&lt;/span&gt;&lt;span&gt; ConstructionError.invalidImage &lt;/span&gt;&lt;span&gt;//this is an error type I made up&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create a variable to hold the pixelBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; pixelBuffer: CVPixelBuffer&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//set some standard attributes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; attrs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [kCVPixelBufferCGImageCompatibilityKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; kCFBooleanTrue,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     kCVPixelBufferCGBitmapContextCompatibilityKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; kCFBooleanTrue] &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; CFDictionary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create the width and height of the buffer to match the image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; width:&lt;/span&gt;&lt;span&gt;Int&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Int&lt;/span&gt;&lt;span&gt;(staticImage.extent.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;.width)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; height:&lt;/span&gt;&lt;span&gt;Int&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Int&lt;/span&gt;&lt;span&gt;(staticImage.extent.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;.height)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create a buffer (notice it uses an in/out parameter for the pixelBuffer variable)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CVPixelBufferCreate&lt;/span&gt;&lt;span&gt;(kCFAllocatorDefault,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    width,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    height,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    kCVPixelFormatType_32BGRA,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    attrs,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    &amp;#x26;&lt;/span&gt;&lt;span&gt;pixelBuffer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create a CIContext&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; context &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIContext&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//use the context to render the image into the pixelBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;context.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(staticImage, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: pixelBuffer&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Though we generally think of &lt;code&gt;CIImage&lt;/code&gt; as an image, Apple is clear that it is not an image by itself. &lt;code&gt;CIImage&lt;/code&gt; needs a context for rendering. This is where a lot of the power of CoreImage and filters and GPU rendering come from, the fact that &lt;code&gt;CIImage&lt;/code&gt; is just the instructions for creating an image. Note: You can also use &lt;code&gt;CGImage&lt;/code&gt; and the &lt;code&gt;CoreGraphics&lt;/code&gt; framework to create pixel buffers. I find it easier to use the &lt;code&gt;CoreImage&lt;/code&gt; framework.&lt;/p&gt;
&lt;h3 id=&quot;configure-avassetwriter&quot;&gt;Configure AVAssetWriter&lt;/h3&gt;
&lt;p&gt;Now with a buffer created, you can configure the &lt;code&gt;AVAssetWriter&lt;/code&gt;. It will take several parameters as a dictionary to determine the dimensions and format of the outputs. You can either set them individually or else Apple provides presets.&lt;br&gt;
One of the most important settings is the output dimension. If the output dimension matches the original image, the video file will show no distortion or letterboxing. However, if the output is smaller, it will compress the image until it fits. If the output is larger, the image will expand until its width or height matches the output size and then will letterbox. Note: if an image expands too much, it will appear grainy. The image below shows different output sizes for a 640 x 480 input image.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;output-sizes&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 766px) 766px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;766&quot; height=&quot;738&quot; src=&quot;https://img.ly/_astro/output-sizes_ZiCM8f.webp&quot; srcset=&quot;/_astro/output-sizes_kVhrb.webp 640w, /_astro/output-sizes_Z1186Wa.webp 750w, /_astro/output-sizes_ZiCM8f.webp 766w&quot;&gt;&lt;/p&gt;
&lt;p&gt;To create your settings for the &lt;code&gt;AVAssetWriter&lt;/code&gt; first create a dictionary containing the different values. The example here sets an output dimension of 400 x 400 and provides for &lt;code&gt;.h264&lt;/code&gt; encoding.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; assetWriterSettings &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [AVVideoCodecKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; AVVideoCodecType.h264, AVVideoWidthKey &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 400&lt;/span&gt;&lt;span&gt;, AVVideoHeightKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 400&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;Any&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To use one of the presets, use the &lt;code&gt;AVOutputSettingsAssistant&lt;/code&gt;. You can read about the different settings in the &lt;a href=&quot;https://developer.apple.com/documentation/avfoundation/avoutputsettingsassistant&quot;&gt;Apple documentation&lt;/a&gt;. An example to create 1080p video output would look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; settingsAssistant &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVOutputSettingsAssistant&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;preset&lt;/span&gt;&lt;span&gt;: .preset1920x1080)&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.videoSettings&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;write-the-pixel-buffer-to-the-video-file&quot;&gt;Write the pixel buffer to the video file&lt;/h3&gt;
&lt;p&gt;With the settings configured, the asset writer can loop through and append the contents of the pixel buffer to create each frame of the video. Something important to notice in the code below is that &lt;code&gt;AVFoundation&lt;/code&gt; has a philosophy to preserve data. So, it will make copies, and it will generally not overwrite files. That is why you have to delete any old files before you can create the new &lt;code&gt;AVAssetWriter&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//generate a file url to store the video. some_image.jpg becomes some_image.mov&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; imageNameRoot &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; imageName.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;separator&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;.&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; outputMovieURL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;urls&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .documentDirectory, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: .userDomainMask).&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;appendingPathComponent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\(imageNameRoot)&lt;/span&gt;&lt;span&gt;.mov&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  throw&lt;/span&gt;&lt;span&gt; ConstructionError.invalidURL &lt;/span&gt;&lt;span&gt;//an error i made up&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//delete any old file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;do&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;removeItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;at&lt;/span&gt;&lt;span&gt;: outputMovieURL)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Could not remove file &lt;/span&gt;&lt;span&gt;\(error.&lt;/span&gt;&lt;span&gt;localizedDescription&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create an assetwriter instance&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; assetwriter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try?&lt;/span&gt;&lt;span&gt; AVAssetWriter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;outputURL&lt;/span&gt;&lt;span&gt;: outputMovieURL, &lt;/span&gt;&lt;span&gt;fileType&lt;/span&gt;&lt;span&gt;: .mov) &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  abort&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//generate 1080p settings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; settingsAssistant &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVOutputSettingsAssistant&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;preset&lt;/span&gt;&lt;span&gt;: .preset1920x1080)&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.videoSettings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create a single video input&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; assetWriterInput &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAssetWriterInput&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mediaType&lt;/span&gt;&lt;span&gt;: .video, &lt;/span&gt;&lt;span&gt;outputSettings&lt;/span&gt;&lt;span&gt;: settingsAssistant)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create an adaptor for the pixel buffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; assetWriterAdaptor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAssetWriterInputPixelBufferAdaptor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;assetWriterInput&lt;/span&gt;&lt;span&gt;: assetWriterInput, &lt;/span&gt;&lt;span&gt;sourcePixelBufferAttributes&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//add the input to the asset writer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;assetwriter.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(assetWriterInput)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//begin the session&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;assetwriter.&lt;/span&gt;&lt;span&gt;startWriting&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;assetwriter.&lt;/span&gt;&lt;span&gt;startSession&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;atSourceTime&lt;/span&gt;&lt;span&gt;: CMTime.zero)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//determine how many frames we need to generate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; framesPerSecond &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 30&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//duration is the number of seconds for the final video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; totalFrames &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; duration &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; framesPerSecond&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; frameCount &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; frameCount &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; totalFrames {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; assetWriterInput.isReadyForMoreMediaData {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; frameTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTimeMake&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Int64&lt;/span&gt;&lt;span&gt;(frameCount), &lt;/span&gt;&lt;span&gt;timescale&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Int32&lt;/span&gt;&lt;span&gt;(framesPerSecond))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //append the contents of the pixelBuffer at the correct time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    assetWriterAdaptor.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(pixelBuffer&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withPresentationTime&lt;/span&gt;&lt;span&gt;: frameTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    frameCount&lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//close everything&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;assetWriterInput.&lt;/span&gt;&lt;span&gt;markAsFinished&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;assetwriter.&lt;/span&gt;&lt;span&gt;finishWriting&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  pixelBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //outputMovieURL now has the video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Logger&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Finished video location: &lt;/span&gt;&lt;span&gt;\(outputMovieURL)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, your image has become a Quicktime video of whatever duration you specified and is ready for use as any other video file! You can now continue with the like of &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;VideoEditor SDK&lt;/a&gt; to &lt;a href=&quot;https://img.ly/blog/new-force-trim-function-for-videoeditor-sdk/&quot;&gt;trim videos&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/blog/how-to-apply-filter-effects-to-video-using-videffects-on-android/&quot;&gt;add filters&lt;/a&gt; or &lt;a href=&quot;https://img.ly/blog/how-to-add-overlays-to-a-video-in-react-native/&quot;&gt;overlays&lt;/a&gt;. You might also want to combine the video with &lt;a href=&quot;https://img.ly/blog/combine-video-clips-into-a-new-file-in-ios-with-swift/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;other video files to make a new creation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Say hi to my dog! A plain video from still images&lt;/p&gt;
&lt;h2 id=&quot;creating-a-blank-video&quot;&gt;Creating a Blank Video&lt;/h2&gt;
&lt;p&gt;The previous example created a single &lt;code&gt;.mov&lt;/code&gt; file for each of your images. The most robust way to get these images into a video with sound, transitions, etc., is to use &lt;code&gt;AVMutableComposition&lt;/code&gt; with multiple tracks and layer instructions. However, for a quick slideshow with just a few elementary transitions, a strategy is to create a blank movie and then use &lt;code&gt;AVVideoComposition&lt;/code&gt; with &lt;code&gt;(asset: AVAsset, applyingCIFiltersWithHandler applier: @escaping (AVAsynchronousCIImageFilteringRequest) -&gt; Void)&lt;/code&gt;. That will let you use &lt;code&gt;CIImage&lt;/code&gt; and &lt;code&gt;CIFilter&lt;/code&gt; to paint each frame of the blank movie with images.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Our basic strategy for this method will be:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a pixel buffer that is the right color space and size and fill it with a solid color&lt;/li&gt;
&lt;li&gt;Create an &lt;code&gt;AVAssetWriter&lt;/code&gt; with a single video input&lt;/li&gt;
&lt;li&gt;Decide how many frames will be required to create a video of the desired duration&lt;/li&gt;
&lt;li&gt;Make a loop and each time through the loop append the pixel buffer&lt;/li&gt;
&lt;li&gt;Create an &lt;code&gt;AVVideoComposition&lt;/code&gt; to add the images to the individual frames of the video&lt;/li&gt;
&lt;li&gt;Let &lt;code&gt;AVPlayer&lt;/code&gt; combine the video and the composition&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the first strategy, you started by making a pixel buffer and using an &lt;code&gt;AVAssetWriter&lt;/code&gt; to create the video file using this code:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; uikitImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;named&lt;/span&gt;&lt;span&gt;: imageName), &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; staticImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt;: uikitImage) &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  throw&lt;/span&gt;&lt;span&gt; ConstructionError.invalidImage &lt;/span&gt;&lt;span&gt;//this is an error type I made up&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, this time, instead of using a source image, you create a &lt;code&gt;CIImage&lt;/code&gt; that is a single color.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; staticImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;color&lt;/span&gt;&lt;span&gt;: bgColor).&lt;/span&gt;&lt;span&gt;cropped&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CGRect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;960&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;540&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;CIImage(color:)&lt;/code&gt; initializer creates an image that is of infinite size and is just a single color. Using the &lt;code&gt;.cropped(to:)&lt;/code&gt; modifier lets you make it the same size as the &lt;code&gt;AVAssetWriter&lt;/code&gt; output so that there won’t be any letterboxing. The rest of the code is the same as before and the end result will be a video file that is just the single color.&lt;/p&gt;
&lt;h3 id=&quot;adding-a-composition&quot;&gt;Adding a Composition&lt;/h3&gt;
&lt;p&gt;Once the video file has been created and saved to disk, load it as an asset, then create an &lt;code&gt;AVVideoComposition&lt;/code&gt;. This allows you to generate individual frames. The function below uses the &lt;code&gt;request&lt;/code&gt; property of the composition. This has a &lt;code&gt;CIImage&lt;/code&gt; representation of the current frame as well as the timestamp of when this frame will appear. The &lt;code&gt;.fetchSlide(forTime:)&lt;/code&gt; is a helper function in the demo app that returns the appropriate &lt;code&gt;CIImage&lt;/code&gt; for the slide to display. Then the &lt;code&gt;.sourceOverCompositing&lt;/code&gt; filter combines the original frame image with the slide. Using a &lt;code&gt;CGAffineTransform&lt;/code&gt; moves the slide across the screen as the video plays.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; createComposition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt; asset: AVAsset) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; slideshowComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVVideoComposition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: asset) {[&lt;/span&gt;&lt;span&gt;weak&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;] request &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt; else&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; slide &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fetchSlide&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forTime&lt;/span&gt;&lt;span&gt;: request.compositionTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; compose &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIFilter.&lt;/span&gt;&lt;span&gt;sourceOverCompositing&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;//filter to join two images&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    compose.backgroundImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; request.sourceImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    compose.inputImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; slide&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transformed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;by&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CGAffineTransform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;translationX&lt;/span&gt;&lt;span&gt;: request.compositionTime.seconds &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 50&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //always finish with the last output of the pipeline&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    request.&lt;/span&gt;&lt;span&gt;finish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: compose.outputImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.outputSlideshow &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; slideshowComposition&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the creation of the video is done asynchronously, you can composite images over the original frame and add filters without the danger of impacting the final frame rate. Recall that &lt;code&gt;CIFilter&lt;/code&gt; works as a pipeline, each &lt;code&gt;CIFilter&lt;/code&gt; has an &lt;code&gt;.outputImage&lt;/code&gt; and most have &lt;code&gt;.inputImage&lt;/code&gt; and some configuration settings. Feeding the output from one filter to the input of the next will let you build complex compositions. Once a composition has been created, &lt;code&gt;AVPlayer&lt;/code&gt; can join the original video with the composition for playback or export.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; item &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayerItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.outputMovie&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;item.videoComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; outputSlideshow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.player &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;playerItem&lt;/span&gt;&lt;span&gt;: item)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above might generate a video like this one.&lt;/p&gt;
&lt;p&gt;As with the first video, the final video is a plain &lt;code&gt;.mov&lt;/code&gt; file.&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;In this tutorial, you saw two ways to convert static images into &lt;code&gt;.mov&lt;/code&gt; files that you can then edit with any video editor. The sample project also has some code for exporting your creation as a &lt;code&gt;.mov&lt;/code&gt; file and code for adding sound to the creation.&lt;/p&gt;
&lt;p&gt;These are not the only ways to go about solving this problem. For example, instead of creating a blank video and overlaying the images, using a stock video of waves at the beach and overlaying the images might work better for your project. Conversely, because the initial pixel buffer is created from a &lt;code&gt;CIImage&lt;/code&gt;, there is no reason that the &lt;code&gt;CIImage&lt;/code&gt; cannot be the end of a long pipeline of &lt;code&gt;CIFilters&lt;/code&gt;. This way the composition is done at the beginning, and the &lt;code&gt;AVAssetWriter&lt;/code&gt; is the only tool needed. The best strategy is the one that makes sense to you and that works with the kinds of media you have.&lt;/p&gt;
&lt;p&gt;Thanks for reading! We hope that you found this tutorial helpful. Feel free to reach out to us on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt; with any questions, comments, or suggestions!&lt;/p&gt;
&lt;p&gt;Looking to integrate video capabilities into your app? Check out our &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;Video Editor SDK&lt;/a&gt;, &lt;a href=&quot;https://img.ly/use-cases/story-reels-short-video-creation&quot;&gt;Short Video Creation&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/products/camera-sdk&quot;&gt;Camera SDK&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To stay in the loop with our latest articles and case studies, subscribe to our &lt;a href=&quot;https://img.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;Newsletter&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2022/02/still-image-to-video-code.png" medium="image"/><category>Video Editing</category><category>Video Editor</category><category>Mobile App Development</category><category>App Development</category><category>Swift</category><category>How-To</category><category>Tech</category><category>Tutorial</category></item><item><title>How to Add a Filter to a Video Stream in iOS</title><link>https://img.ly/blog/how-to-add-a-filter-to-a-video-stream-in-ios/</link><guid isPermaLink="true">https://img.ly/blog/how-to-add-a-filter-to-a-video-stream-in-ios/</guid><description>Applying stunning filters to videos requires different methods than applying them to files in iOS. Find out how!</description><pubDate>Mon, 08 Nov 2021 15:33:55 GMT</pubDate><content:encoded>&lt;p&gt;Using filters and effects with video streams requires different strategies than applying them to files. In this tutorial, you will see how to apply effects and filters to video streams in real-time. The code in this tutorial compiles with Xcode 13.&lt;/p&gt;
&lt;p&gt;As with most &lt;code&gt;AVFoundation&lt;/code&gt; code, the Xcode simulator is not the best platform for running the code. Test on an actual device. A project with code that supports this tutorial can be found &lt;a href=&quot;https://github.com/waltertyree/filter-video-stream&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;streams-or-files&quot;&gt;Streams or Files&lt;/h2&gt;
&lt;p&gt;For video files stored on the device, you can use an &lt;code&gt;AVMutableVideoComposition&lt;/code&gt; to apply filters. When streaming video from a remote location, this strategy will not work. The &lt;code&gt;AVMutuableVideoComposition&lt;/code&gt; classes are not designed to work in real-time with streams. Apple provides a way to extract the pixels from the stream at regular intervals. You can manipulate the pixels and then render them to the screen. An &lt;code&gt;AVPlayerItemVideoOutput&lt;/code&gt; object gives access to the pixels that make up a video frame. Apple also supplies a &lt;code&gt;CADisplayLink&lt;/code&gt; object to ensure you are extracting the right pixels at the right time. The display link is a specialized timer that will fire in sync with the redraw rate of the current display.&lt;/p&gt;
&lt;p&gt;So, to filter a video stream, the strategy becomes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set up an AVPlayer as normal and assign it the URL of the stream&lt;/li&gt;
&lt;li&gt;Attach an &lt;code&gt;AVPlayerItemVideoOutput&lt;/code&gt; object to the stream&lt;/li&gt;
&lt;li&gt;Attach a &lt;code&gt;CADisplayLink&lt;/code&gt; object to the run loop&lt;/li&gt;
&lt;li&gt;Extract the current pixel buffer from the AVPlayerItem every time the screen redraws&lt;/li&gt;
&lt;li&gt;Convert the pixel buffer to a CoreImage object&lt;/li&gt;
&lt;li&gt;Apply filters&lt;/li&gt;
&lt;li&gt;Display the image&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;setting-up-avplayer&quot;&gt;Setting up AVPlayer&lt;/h2&gt;
&lt;p&gt;You won’t use the &lt;code&gt;AVPlayerLayer&lt;/code&gt; or &lt;code&gt;AVPlayerViewController&lt;/code&gt; to display the video, but the &lt;code&gt;AVPlayer&lt;/code&gt; plays a central role. The &lt;code&gt;AVPlayer&lt;/code&gt; keeps the video in sync with the audio, handles decoding whatever format the stream uses, controls buffering, and more. The basic setup is the same as with any other project:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create a player&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; videoItem &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayerItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: streamURL)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.player &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;playerItem&lt;/span&gt;&lt;span&gt;: videoItem)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The URL can either point to a local resource or a stream.&lt;/p&gt;
&lt;h2 id=&quot;using-avplayeritemvideooutput&quot;&gt;Using AVPlayerItemVideoOutput&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;AVPlayerItemVideoOutput&lt;/code&gt; allows you to query the &lt;code&gt;AVPlayerItem&lt;/code&gt; for the pixel buffer (one screen worth of pixels) at any given time. You can specify different pixel formats and other options for the output. For this tutorial, you will you can create the object with a simple instantiation using the default formats.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let playerItemVideoOutput = AVPlayerItemVideoOutput()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Later you will add this video output to your player item. Then you can ask it to generate the pixel buffers as needed. In this tutorial, you will ask for the pixel buffer of the current frame. You could ask for the frame for any timestamp though.&lt;/p&gt;
&lt;h2 id=&quot;creating-the-display-link&quot;&gt;Creating the Display Link&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;CADisplayLink&lt;/code&gt; class is a specialized &lt;code&gt;NSTimer&lt;/code&gt; that synchronizes itself to the screen refresh rate of the display. This ensures that the pixel buffer you extract will be from the correct time of your video stream. It will get rendered at the correct time in the screen refresh. Syncing a standard &lt;code&gt;NSTimer&lt;/code&gt; to the screen refresh has always been problematic. With the new variable refresh rate screens that Apple makes, it becomes impossible. However, &lt;code&gt;CADisplayLink&lt;/code&gt; adjusts its rate as the screen refresh rate changes. Apple explains how this works in the &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10147/&quot;&gt;2021 WWDC Session &lt;em&gt;Optimize for Variable Refresh Rate Displays&lt;/em&gt;&lt;/a&gt;. When you create the display link, you give it the name of a function to call each time it executes. A sample for creating a link could look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;lazy&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt; displayLink: CADisplayLink &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CADisplayLink&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                                  selector&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#selector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;displayLinkFired&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;link:&lt;/span&gt;&lt;span&gt;)))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Elsewhere in the code, a function called &lt;code&gt;displayLinkFired(link: CADisplayLink)&lt;/code&gt; extracts the pixel buffer from the &lt;code&gt;AVPlayerItem&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;starting-the-player&quot;&gt;Starting the Player&lt;/h2&gt;
&lt;p&gt;Apple notes that loading a video file takes a measurable amount of time. It can take even longer when the video file is streaming across a network. AVFoundation allows your program to continue to execute while the setup steps and initial buffering are occurring in the background. You can run into issues if you load a video into AVFoundation and then immediately attempt to work with it. AVFoundation may not show an error or status message, but it will not perform as expected either.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;most important&lt;/em&gt; step in this entire process is using an observer to wait until the AVPlayerItem has a &lt;code&gt;.readyToPlay&lt;/code&gt; status before attaching the output and displaying link objects. In the example below &lt;code&gt;statusOberserver&lt;/code&gt;, &lt;code&gt;player&lt;/code&gt;, &lt;code&gt;playerItemVideoOutput&lt;/code&gt; and &lt;code&gt;displayLink&lt;/code&gt; are all declared at the class level. They need to persist the entire time the video is being used.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create a player&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; videoItem &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayerItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: streamURL&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.player &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;playerItem&lt;/span&gt;&lt;span&gt;: videoItem) &lt;/span&gt;&lt;span&gt;//1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//*important* add the display link and the output only after it is ready to play&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.statusObserver &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; videoItem.&lt;/span&gt;&lt;span&gt;observe&lt;/span&gt;&lt;span&gt;(\.status, &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                       options&lt;/span&gt;&lt;span&gt;: [.new, .old],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                 changeHandler&lt;/span&gt;&lt;span&gt;: { playerItem, change &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; playerItem.status &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; .readyToPlay { &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      playerItem.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.playerItemVideoOutput)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      self&lt;/span&gt;&lt;span&gt;.displayLink.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: .main, &lt;/span&gt;&lt;span&gt;forMode&lt;/span&gt;&lt;span&gt;: .common)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      self&lt;/span&gt;&lt;span&gt;.player&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;play&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;//4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is what to notice about the code above&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creating the player will take a large amount of time, but the program execution will not stop and wait&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NSKeyValueObservation&lt;/code&gt; is how swift defines key value observers the code will execute every time the &lt;code&gt;videoItem.status&lt;/code&gt; property changes values&lt;/li&gt;
&lt;li&gt;Checks to see if the &lt;code&gt;.status&lt;/code&gt; property is &lt;code&gt;.readyToPlay&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;After adding the display link and the video output objects start playing the video&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;extracting-the-pixel-buffer&quot;&gt;Extracting the Pixel Buffer&lt;/h2&gt;
&lt;p&gt;Once the video begins to play, the display link function gets called for each screen refresh. Here is an example of how to extract the pixel buffer and render it to a &lt;code&gt;UIImageView&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;@objc&lt;/span&gt;&lt;span&gt; func&lt;/span&gt;&lt;span&gt; displayLinkFired&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;: CADisplayLink) { &lt;/span&gt;&lt;span&gt;//1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; currentTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; playerItemVideoOutput.&lt;/span&gt;&lt;span&gt;itemTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forHostTime&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CACurrentMediaTime&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; playerItemVideoOutput.&lt;/span&gt;&lt;span&gt;hasNewPixelBuffer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forItemTime&lt;/span&gt;&lt;span&gt;: currentTime) { &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; buffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; playerItemVideoOutput.&lt;/span&gt;&lt;span&gt;copyPixelBuffer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forItemTime&lt;/span&gt;&lt;span&gt;: currentTime, &lt;/span&gt;&lt;span&gt;itemTimeForDisplay&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      let&lt;/span&gt;&lt;span&gt; frameImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cvImageBuffer&lt;/span&gt;&lt;span&gt;: buffer) &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      //4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      let&lt;/span&gt;&lt;span&gt; pixelate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIFilter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;CIPixellate&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      pixelate.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(frameImage, &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputImageKey)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      pixelate.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.filterSlider.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputScaleKey)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      pixelate.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CIVector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;: frameImage.extent.midX, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: frameImage.extent.midY), &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputCenterKey)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     let&lt;/span&gt;&lt;span&gt; newFrame &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; pixelate.outputImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cropped&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: frameImage.extent)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     self&lt;/span&gt;&lt;span&gt;.videoView.&lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; UIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ciImage&lt;/span&gt;&lt;span&gt;: newFrame) &lt;/span&gt;&lt;span&gt;//5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is what the code is doing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Marks the &lt;code&gt;func&lt;/code&gt; with &lt;code&gt;@objc&lt;/code&gt; so that it works with the display link selector&lt;/li&gt;
&lt;li&gt;Asks if there is a new pixel buffer to display. Depending on the refresh rate of the screen and the frame rate of the video, there will not always be a new buffer.&lt;/li&gt;
&lt;li&gt;After extracting the pixel buffer, convert it to a CoreImage object&lt;/li&gt;
&lt;li&gt;Apply any filters. This example applies the &lt;code&gt;CIPixellate&lt;/code&gt; filter. It uses a slider to let the user change the intensity as the video plays&lt;/li&gt;
&lt;li&gt;Convert the filtered image to a &lt;code&gt;UIImage&lt;/code&gt; and assign it to the &lt;code&gt;UIImageView&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now as the pixellate scale changes the image will be filtered but the video will continue to play smoothly.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;video-intensity-iOS-filter-video-stream&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1000px) 1000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1000&quot; height=&quot;497&quot; src=&quot;https://img.ly/_astro/video-intensity-iOS-filter-video-stream_Z29kdA1.webp&quot; srcset=&quot;/_astro/video-intensity-iOS-filter-video-stream_VOjmc.webp 640w, /_astro/video-intensity-iOS-filter-video-stream_Z2eFwGs.webp 750w, /_astro/video-intensity-iOS-filter-video-stream_1oxxcg.webp 828w, /_astro/video-intensity-iOS-filter-video-stream_Z29kdA1.webp 1000w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;Using the strategy above you should be able to apply different filters to the video stream in real-time. Remember that you only have a few milliseconds though, then the video will begin to stutter. CoreImage filters are optimized to make use of the GPU and should be fast enough in most cases. For better performance, you can render the filtered image to an &lt;code&gt;MTKView&lt;/code&gt; and use Metal. Apple has also begun to create Metal Performance Shaders which are even more efficient than CoreImage filters when used with a MetalKit view. As of now, there are many more CoreImage filters, so that may give you the most flexibility.&lt;br&gt;
The strategy in this tutorial is adequate for filtering video streams. If you want to give your users more flexibility with filters and other video tweaks, an editor such as &lt;a href=&quot;https://img.ly/products/video-sdk/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditorSDK&lt;/a&gt; can let you focus on your application’s core functions and let a team of professionals worry about the video. Using VideoEditorSDK your users can apply filters to video as well as text annotations and more.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;stream-edit-ios&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 600px) 600px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;600&quot; height=&quot;640&quot; src=&quot;https://img.ly/_astro/stream-edit-ios_Z1MTqzv.webp&quot; srcset=&quot;/_astro/stream-edit-ios_Z1MTqzv.webp 600w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Looking for more video capabilities? Check out our solutions for &lt;a href=&quot;https://img.ly/use-cases/story-reels-short-video-creation&quot;&gt;Short Video Creation&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/products/camera-sdk&quot;&gt;Camera SDK&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Thanks for reading! We hope that you found this tutorial helpful. Feel free to reach out to us on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt; with any questions or suggestions.&lt;/p&gt;
&lt;p&gt;You like what we do? Check our &lt;a href=&quot;https://img.ly/company/careers&quot;&gt;careers page&lt;/a&gt;. Even if you can’t find your role listed, we might be interested since you are already here!&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2021/11/video-filter-ios-video-stream.jpg" medium="image"/><category>iOS App Development</category><category>iOS</category><category>Video Editing</category><category>App Development</category><category>Mobile App Development</category><category>Video Filter</category><category>Apple</category><category>Developer Tools</category><category>How-To</category><category>Tutorial</category></item><item><title>How To Record Screen Video Using ReplayKit for iOS</title><link>https://img.ly/blog/record-screen-with-swift-replaykit/</link><guid isPermaLink="true">https://img.ly/blog/record-screen-with-swift-replaykit/</guid><description>Use Swift and ReplayKit to add screen recording to your application. Also use the new rolling clips feature introduced in iOS 15!</description><pubDate>Tue, 21 Sep 2021 15:05:08 GMT</pubDate><content:encoded>&lt;p&gt;In this article, you will see how to use Swift and &lt;code&gt;ReplayKit&lt;/code&gt; to add screen recording to your application. You will also see how to use the new rolling clips feature, introduced in iOS 15. The code in this article uses Swift 5 and Xcode 13. Clone &lt;a href=&quot;https://github.com/waltertyree/replay-screen-video&quot;&gt;this repository&lt;/a&gt; for a sample project and example code.&lt;/p&gt;
&lt;h2 id=&quot;replaykit-basics&quot;&gt;ReplayKit Basics&lt;/h2&gt;
&lt;p&gt;Apple introduced the &lt;code&gt;ReplayKit&lt;/code&gt; framework in 2015 with iOS 9. They have refined and expanded it since then, but it still has a small API. &lt;code&gt;ReplayKit&lt;/code&gt; uses the same shared components as AirPlay and the device screen recorder (accessible from the device Control Center). For this reason, &lt;code&gt;ReplayKit&lt;/code&gt; functions will not work when using AirPlay or playing video. Also, the screen recording functions do not work on the Xcode simulators. You should test using a physical device.&lt;/p&gt;
&lt;p&gt;Your app makes all its calls to the &lt;code&gt;RPScreenRecorder.shared()&lt;/code&gt; object. This is a singleton object for your app that &lt;code&gt;ReplayKit&lt;/code&gt; provides. There is no need to store it in a variable or pass it around in your code.&lt;/p&gt;
&lt;h2 id=&quot;rolling-clip-recording&quot;&gt;Rolling Clip Recording&lt;/h2&gt;
&lt;p&gt;In iOS 15, Apple introduced rolling clip recording. &lt;code&gt;ReplayKit&lt;/code&gt; will maintain an updated video buffer of the most recent fifteen seconds. Your app can request the video at any time. &lt;code&gt;ReplayKit&lt;/code&gt; comes from &lt;code&gt;GameCenter&lt;/code&gt;. Apple’s idea for this feature is for creating a short, sharable video of the moments before an exciting event in a game. Another use can be for a user to record the steps leading up to a defect during testing. When the user is unlikely to know in advance that they will want a screen recording, rolling clips may be a good solution.&lt;/p&gt;
&lt;p&gt;To start rolling clip buffering, include a method such as this one&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RPScreenRecorder.&lt;/span&gt;&lt;span&gt;shared&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;startClipBuffering&lt;/span&gt;&lt;span&gt; { err &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; err {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //either the user denied permission or something like&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //AVPlayer or AirPlay is using the shared recorder resources.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error attempting to start buffering &lt;/span&gt;&lt;span&gt;\(err.&lt;/span&gt;&lt;span&gt;localizedDescription&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Clip buffering started.&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Screen recording has privacy implications. &lt;code&gt;ReplayKit&lt;/code&gt; presents the user with a modal, requesting to record before it starts. If the user denies the request or if the buffering is unable to start, &lt;code&gt;ReplayKit&lt;/code&gt; passes an &lt;code&gt;Error&lt;/code&gt; object to the closure. Unlike other privacy modals, you do not have an opportunity to add your own language to the message. The modal appears as soon as you execute the &lt;code&gt;.startClipBuffering&lt;/code&gt; command, so be sure that the user is not surprised by it. Execute the command at a logical transition in your application. After the user grants permissions, the modal will not appear again unless eight minutes pass before the next call to &lt;code&gt;.startClipBuffering&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;permissions-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 250px) 250px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;250&quot; height=&quot;193&quot; src=&quot;https://img.ly/_astro/permissions-1_2qx6ET.webp&quot; srcset=&quot;/_astro/permissions-1_2qx6ET.webp 250w&quot;&gt;&lt;/p&gt;
&lt;p&gt;While the clip buffering runs, at any time, you can save the buffer to a URL on the device. The clip you save can be any duration up to fifteen seconds. If the recorder is unable to write the clip to disk, it will send an error.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; clipURL: URL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fileURLWithPath&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;NSTemporaryDirectory&lt;/span&gt;&lt;span&gt;()).&lt;/span&gt;&lt;span&gt;appendingPathComponent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\(&lt;/span&gt;&lt;span&gt;UUID&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.mov&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RPScreenRecorder.&lt;/span&gt;&lt;span&gt;shared&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;exportClip&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: clipURL, &lt;/span&gt;&lt;span&gt;duration&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;TimeInterval&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;15&lt;/span&gt;&lt;span&gt;)) { err &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; err {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;An error? &lt;/span&gt;&lt;span&gt;\(err.&lt;/span&gt;&lt;span&gt;localizedDescription&lt;/span&gt;&lt;span&gt; )&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Clip saved to url &lt;/span&gt;&lt;span&gt;\(clipURL)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above stores a fifteen-second clip at a URL in the user’s temporary directory. The temporary directory gets cleared by the system, but not while the app is running. To ensure that the files are not deleted except by the user, store them in the user’s documents directory instead.&lt;br&gt;
When your app is done collecting video, use &lt;code&gt;.stopClipBuffering&lt;/code&gt; to allow the system to clean up resources and stop the recording.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RPScreenRecorder.&lt;/span&gt;&lt;span&gt;shared&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;stopClipBuffering&lt;/span&gt;&lt;span&gt; { err &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; err {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error attempting to stop buffering &lt;/span&gt;&lt;span&gt;\(err.&lt;/span&gt;&lt;span&gt;localizedDescription&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Clip buffering stopped.&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;recording-screen-and-app-audio&quot;&gt;Recording Screen and App Audio&lt;/h2&gt;
&lt;p&gt;Rolling clip buffering runs in the background. You can also allow the user to control when to start and stop screen recording. The API looks similar and is available since iOS 10.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RPScreenRecorder.&lt;/span&gt;&lt;span&gt;shared&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;startRecording&lt;/span&gt;&lt;span&gt; { err &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  guard&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; else&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(err.&lt;/span&gt;&lt;span&gt;debugDescription&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //code to display recording indicator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As with the rolling clip buffering, the system will display a permissions dialog. If the user denies permission you will get an error and the recording will not start.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;recording-permissions-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 250px) 250px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;250&quot; height=&quot;193&quot; src=&quot;https://img.ly/_astro/recording-permissions-1_ZEkXHU.webp&quot; srcset=&quot;/_astro/recording-permissions-1_ZEkXHU.webp 250w&quot;&gt;&lt;/p&gt;
&lt;p&gt;The same eight-minute rule applies as for rolling clip recording. But, since the user has just taken some action to start recording, they should expect to see this modal.&lt;br&gt;
Apple provides a basic view controller for editing and sharing the video.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ReplayKit&lt;/code&gt; passes the &lt;code&gt;RPPreviewViewController&lt;/code&gt; to the &lt;code&gt;.stopRecording&lt;/code&gt; handler.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RPScreenRecorder.&lt;/span&gt;&lt;span&gt;shared&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;stopRecording&lt;/span&gt;&lt;span&gt; { preview, err &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; preview &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; preview &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;no preview window&quot;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //update recording controls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  preview.modalPresentationStyle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; .overFullScreen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  preview.previewControllerDelegate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;present&lt;/span&gt;&lt;span&gt;(preview, &lt;/span&gt;&lt;span&gt;animated&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;preview&lt;/code&gt; object above is the &lt;code&gt;RPPreviewViewController&lt;/code&gt; editor. You can display it as a modal or as a popover depending on your needs. The Apple provided editor will allow the user to save to their camera roll or share the clip after editing.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;apple-editor-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 400px) 400px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;400&quot; height=&quot;866&quot; src=&quot;https://img.ly/_astro/apple-editor-1_5tScc.webp&quot; srcset=&quot;/_astro/apple-editor-1_5tScc.webp 400w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Notice in the code above that there is a &lt;code&gt;previewControllerDelegate&lt;/code&gt;. You will need to add some code to the delegate to cleanup and dismiss the editor when the user is done sharing or saving. An example is below.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; previewControllerDidFinish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt; previewController: RPPreviewViewController) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  previewController.&lt;/span&gt;&lt;span&gt;dismiss&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;animated&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;) { [&lt;/span&gt;&lt;span&gt;weak&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //reset the UI and show the recording controls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;using-many-uiwindow-objects&quot;&gt;Using Many &lt;code&gt;UIWindow&lt;/code&gt; Objects&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;ReplayKit&lt;/code&gt; recording will only capture the main window of your application. When designing for screen recording, you can place any UI elements in a separate &lt;code&gt;UIWindow&lt;/code&gt; object. Although applications typically only have one &lt;code&gt;UIWindow&lt;/code&gt;, iOS allows many windows. A &lt;code&gt;UIWindow&lt;/code&gt; requires a &lt;code&gt;.rootViewController&lt;/code&gt; to provide content. Then use it like any other view in your application.&lt;br&gt;
The UI for this application consists of three &lt;code&gt;UIWindow&lt;/code&gt; objects.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;combined-windows&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 300px) 300px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;300&quot; height=&quot;649&quot; src=&quot;https://img.ly/_astro/combined-windows_ZrRaan.webp&quot; srcset=&quot;/_astro/combined-windows_ZrRaan.webp 300w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Apple places the status bar in a separate window from your application. The recording controls are in a separate window from the main application and will not appear in the screen recording.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;exploded-windows&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 503px) 503px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;503&quot; height=&quot;360&quot; src=&quot;https://img.ly/_astro/exploded-windows_1AjROT.webp&quot; srcset=&quot;/_astro/exploded-windows_1AjROT.webp 503w&quot;&gt;&lt;/p&gt;
&lt;p&gt;To add some recording controls or a recording indicator to your app. Create a new &lt;code&gt;UIWindow&lt;/code&gt; and add it to the same &lt;code&gt;windowScene&lt;/code&gt; as your view.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;controlsWindow &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIWindow&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;frame&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CGRect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;: view.frame.width, &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;45&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; view.safeAreaInsets.top)) &lt;/span&gt;&lt;span&gt;//1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;controlsWindow&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.windowScene &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; view.window&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.windowScene &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;controlsWindow&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;makeKeyAndVisible&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; recordingIndicator &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIButton.&lt;/span&gt;&lt;span&gt;systemButton&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;UIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;systemName&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;record.circle&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#selector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;recordingToggled&lt;/span&gt;&lt;span&gt;(_:))) &lt;/span&gt;&lt;span&gt;//4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; vc &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIViewController&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;controlsWindow&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.rootViewController &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; vc &lt;/span&gt;&lt;span&gt;//5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vc.&lt;/span&gt;&lt;span&gt;view&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;addSubview&lt;/span&gt;&lt;span&gt;(recordingIndicator) &lt;/span&gt;&lt;span&gt;//6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;recordingIndicator.center &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CGPoint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;: vc.&lt;/span&gt;&lt;span&gt;view&lt;/span&gt;&lt;span&gt;.center.x, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: vc.&lt;/span&gt;&lt;span&gt;view&lt;/span&gt;&lt;span&gt;.center.y &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 20&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above creates the red/blue recording button and banner at the top of the view. It relies on a &lt;code&gt;controlsWindow&lt;/code&gt; variable created at the class level. Here is what it is doing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creates a new &lt;code&gt;UIWindow&lt;/code&gt; that is the width of the view and tall enough for the button and the safe area (the area around the notch and status bar)&lt;/li&gt;
&lt;li&gt;Adds the window to the same &lt;code&gt;windowScene&lt;/code&gt; as the current view&lt;/li&gt;
&lt;li&gt;Makes the window visible (&lt;code&gt;UIWindow&lt;/code&gt; objects are &lt;code&gt;hidden&lt;/code&gt; when created) and brings it to the front of the stack of windows&lt;/li&gt;
&lt;li&gt;Creates a button and links it to a method in your class &lt;code&gt;recordingToggled(_:)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Adds a view controller to the window so that there will be some content&lt;/li&gt;
&lt;li&gt;Adds the button to the view controller&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Because the recording indicator and button are in a separate window, they will not appear in any screen recording. Be mindful when showing modal views or alert views, as they may appear behind the extra window. A good strategy is to hide the extra window when showing these views. You can use a similar strategy to hide any other UI elements that should not appear in screen recordings.&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;Though Apple provides a basic editor, you can provide your own editing tools. The examples above showed the rolling clip editor writes the video to the disk. The regular recorder can also write the video to disk. You can stop recording using this code:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; clipURL: URL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fileURLWithPath&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;NSTemporaryDirectory&lt;/span&gt;&lt;span&gt;()).&lt;/span&gt;&lt;span&gt;appendingPathComponent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\(&lt;/span&gt;&lt;span&gt;UUID&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.mov&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RPScreenRecorder.&lt;/span&gt;&lt;span&gt;shared&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;stopRecording&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;withOutput&lt;/span&gt;&lt;span&gt;: clipURL) { err &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //check for an error and process the url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This method would replace the &lt;code&gt;.stopRecording { preview, err in&lt;/code&gt; method shown above.&lt;br&gt;
With the video saved, you can use an editor such as the &lt;a href=&quot;https://img.ly/products/video-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditor SDK&lt;/a&gt; to allow your users to add annotations, text and filters to their clips. They can even add audio or combine clips to make a great creation.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;with-editor&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 400px) 400px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;400&quot; height=&quot;866&quot; src=&quot;https://img.ly/_astro/with-editor_26cWBv.webp&quot; srcset=&quot;/_astro/with-editor_26cWBv.webp 400w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this article, you saw how to capture a rolling screen recording as well as how to capture the screen manually. Further, you saw how using an SDK such as &lt;a href=&quot;https://img.ly/products/video-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditor SDK&lt;/a&gt; allows you to annotate and enhance your clips before sharing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading! We hope that you found this tutorial helpful. Feel free to reach out to us on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt; with any questions, comments, or suggestions!&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2021/09/screen-record-video-on-app.jpg" medium="image"/><category>iOS App Development</category><category>iOS</category><category>ReplayKit</category><category>Apple</category><category>Mobile App Development</category><category>Video Editing</category><category>How-To</category><category>Tutorial</category></item><item><title>How to Trim and Crop Video in Swift</title><link>https://img.ly/blog/trim-and-crop-video-in-swift/</link><guid isPermaLink="true">https://img.ly/blog/trim-and-crop-video-in-swift/</guid><description>Learn how to use Swift and AVKit to crop a video clip and trim a video timeline. </description><pubDate>Tue, 31 Aug 2021 16:03:25 GMT</pubDate><content:encoded>&lt;p&gt;In this article, you will see how to use Swift and &lt;code&gt;AVKit&lt;/code&gt; to crop a video clip and how to trim a video’s timeline. Then you will learn how to use &lt;code&gt;AVAssetExportSession&lt;/code&gt; to write your edited video to disk. The code in this article uses Swift 5 and Xcode 12.5. Clone &lt;a href=&quot;https://github.com/waltertyree/trim-crop-video&quot;&gt;this repository&lt;/a&gt; for a sample project and example code.&lt;/p&gt;
&lt;h2 id=&quot;anatomy-of-a-video-file&quot;&gt;Anatomy of a Video File&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;AVFoundation&lt;/code&gt; and &lt;code&gt;AVKit&lt;/code&gt; frameworks are what Swift and iOS use to manage audio and video. When starting out, you may use an &lt;code&gt;AVPlayerViewController&lt;/code&gt; for playback with the same controls and features as the native players. You can also use an &lt;code&gt;AVPlayer&lt;/code&gt; object and provide your own playback and editing controls. Whichever you choose, you begin by loading a media file (&lt;code&gt;.mp3&lt;/code&gt;, &lt;code&gt;.mov&lt;/code&gt;, etc.) into an &lt;code&gt;AVPlayerItem&lt;/code&gt;. Inside the &lt;code&gt;AVPlayerItem&lt;/code&gt;, the media becomes an &lt;code&gt;AVAsset&lt;/code&gt; which may have many &lt;code&gt;tracks&lt;/code&gt; of video, audio, text, closed captions etc.&lt;/p&gt;
&lt;p&gt;To manipulate an &lt;code&gt;AVAsset&lt;/code&gt;, Apple provides the &lt;code&gt;AVComposition&lt;/code&gt; class. The &lt;code&gt;AVComposition&lt;/code&gt; can act on a single track or many tracks to filter and mix underlying media and produces a single output. An &lt;code&gt;AVPlayer&lt;/code&gt; shows the output of an &lt;code&gt;AVComposition&lt;/code&gt; on a device. When it is time to export, you can use an &lt;code&gt;AVAssetExportSession&lt;/code&gt; with the same &lt;code&gt;AVComposition&lt;/code&gt; to write to disk or upload to a server.&lt;/p&gt;
&lt;h2 id=&quot;cropping-video-to-a-rectangle&quot;&gt;Cropping Video to a Rectangle&lt;/h2&gt;
&lt;p&gt;Apple provides some different &lt;code&gt;AVComposition&lt;/code&gt; classes optimized for common tasks. Apple recommends using one of these classes instead of writing custom &lt;code&gt;AVComposition&lt;/code&gt; classes whenever possible. The reason for this is that when Apple introduces new technologies (like HDR Video), they will ensure it works with their classes. If you’ve written your own, you will have to update it to work with the new technologies. In this article you will crop the video to a rectangle and let the audio pass through as recorded. A good class to use for this task will be the &lt;code&gt;AVMutableVideoComposition&lt;/code&gt; with the &lt;code&gt;init(asset: AVAsset, applyingCIFiltersWithHandler applier: @escaping (AVAsynchronousCIImageFilteringRequest) -&gt; Void)&lt;/code&gt; initializer. This will allow you to apply a &lt;code&gt;CIFilter&lt;/code&gt; to each frame of the video. Apple provides an efficient cropping filter called &lt;code&gt;CICrop&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; transformVideo&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;: AVPlayerItem, &lt;/span&gt;&lt;span&gt;cropRect&lt;/span&gt;&lt;span&gt;: CGRect) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; cropScaleComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVMutableVideoComposition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: item.asset, &lt;/span&gt;&lt;span&gt;applyingCIFiltersWithHandler&lt;/span&gt;&lt;span&gt;: {request &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; cropFilter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIFilter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;CICrop&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt; //1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cropFilter.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(request.sourceImage, &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputImageKey) &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cropFilter.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CIVector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cgRect&lt;/span&gt;&lt;span&gt;: cropRect), &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;inputRectangle&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; imageAtOrigin &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cropFilter.outputImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transformed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;by&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CGAffineTransform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;translationX&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cropRect.origin.x, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cropRect.origin.y)) &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    request.&lt;/span&gt;&lt;span&gt;finish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: imageAtOrigin, &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;//4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  cropScaleComposition.renderSize &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cropRect.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt; //5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  item.videoComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cropScaleComposition  &lt;/span&gt;&lt;span&gt;//6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;applyingCIFiltersWithHandler&lt;/code&gt; will execute for every frame in the &lt;code&gt;asset&lt;/code&gt; of the &lt;code&gt;AVPlayerItem&lt;/code&gt;. Here is what the code above will do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a CICrop filter (note that this demo uses &lt;code&gt;!&lt;/code&gt; to force unwrap, production code should handle failure)&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;.sourceImage&lt;/code&gt; (a &lt;code&gt;CIImage&lt;/code&gt;) from the request to the filter.&lt;/li&gt;
&lt;li&gt;Move the cropped image to the origin of the video frame. When you resize the frame (step 4) it will resize from the origin.&lt;/li&gt;
&lt;li&gt;Output the transformed frame image&lt;/li&gt;
&lt;li&gt;Set the size of the video frame to the cropped size&lt;/li&gt;
&lt;li&gt;Attach the composition to the &lt;code&gt;videoComposition&lt;/code&gt; property of the &lt;code&gt;asset&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notice that the transformation does not alter the underlying asset. It creates a &lt;code&gt;videoComposition&lt;/code&gt; filter to attach to the item during playback. After the transformation, &lt;code&gt;AVPlayer&lt;/code&gt; will display the new creation when executing its &lt;code&gt;.play()&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;Sharp eyed readers will have noticed that the &lt;code&gt;init&lt;/code&gt; method mentions “applying &lt;em&gt;CIFilters&lt;/em&gt;” plural. Instead of one filter, you can create an entire pipeline of &lt;code&gt;CIFilter&lt;/code&gt; objects to manipulate the visual properties of the frames. The &lt;code&gt;request&lt;/code&gt; object also contains the &lt;code&gt;compositionTime&lt;/code&gt; so filters can change at different parts of the video.&lt;/p&gt;
&lt;h2 id=&quot;trimming-the-time-of-a-video&quot;&gt;Trimming the Time of a Video&lt;/h2&gt;
&lt;p&gt;When an &lt;code&gt;AVPlayer&lt;/code&gt; loads an &lt;code&gt;AVItem&lt;/code&gt; the start time of the video will be at &lt;code&gt;CMTime.zero&lt;/code&gt;. The end will be the &lt;code&gt;duration&lt;/code&gt; property of the &lt;code&gt;AVItem&lt;/code&gt;. To adjust the playback times, you call the &lt;code&gt;.seek&lt;/code&gt; function to move to the new start time and set the &lt;code&gt;forwardPlaybackEndTime&lt;/code&gt; to the new end time. Now the player will only play the part of the clip between those times.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//The player object is already created and configured to play video in the ViewController&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//load a video .mov file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.player &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;grocery-train&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mov&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Set the start time to 5 seconds.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; startTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTimeMakeWithSeconds&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;preferredTimescale&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Convert the duration of the video to seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; videoDurationInSeconds &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.player&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.currentItem&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.duration.seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Subtract 5 seconds from the end time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; endTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTimeMakeWithSeconds&lt;/span&gt;&lt;span&gt;(videoDurationInSeconds &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;preferredTimescale&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Assign the new values to the start and end time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.player&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;seek&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: startTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.player&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.currentItem&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.forwardPlaybackEndTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; endTime&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Play the video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.player&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;play&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;CMTime&lt;/code&gt; (Core Media Time) object uses timescales and &lt;code&gt;Int&lt;/code&gt; values to map to the individual frames of tracks. Video tracks commonly come to your app with 24, 30, 60 or 120 frames per second. Converting between these different formats using Floats or Doubles would be imprecise. When converting seconds to &lt;code&gt;CMTime&lt;/code&gt; with video, &lt;code&gt;600&lt;/code&gt; is the commonly used timescale because it is a common multiple of all of the standard frames per second.&lt;/p&gt;
&lt;h2 id=&quot;exporting-the-video&quot;&gt;Exporting the Video&lt;/h2&gt;
&lt;p&gt;As with the &lt;code&gt;videoComposition&lt;/code&gt; property, setting the start and end times of the player only modifies the playback of the video clip. The underlying asset remains unchanged. When you use the &lt;code&gt;AVAssetExportSession&lt;/code&gt;, the new video file will have the change. The export session applies time range and video composition objects as it writes the file. A function to export might look like:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; export&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt; asset: AVAsset, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; outputMovieURL: URL, &lt;/span&gt;&lt;span&gt;startTime&lt;/span&gt;&lt;span&gt;: CMTime, &lt;/span&gt;&lt;span&gt;endTime&lt;/span&gt;&lt;span&gt;: CMTime, &lt;/span&gt;&lt;span&gt;composition&lt;/span&gt;&lt;span&gt;: AVVideoComposition) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //Create trim range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; timeRange &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTimeRangeFromTimeToTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;: startTime, &lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;span&gt;: endTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //delete any old file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  do&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;removeItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;at&lt;/span&gt;&lt;span&gt;: outputMovieURL)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Could not remove file &lt;/span&gt;&lt;span&gt;\(error.&lt;/span&gt;&lt;span&gt;localizedDescription&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //create exporter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; exporter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAssetExportSession&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: asset, &lt;/span&gt;&lt;span&gt;presetName&lt;/span&gt;&lt;span&gt;: AVAssetExportPresetHighestQuality)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //configure exporter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.videoComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; composition&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.outputURL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; outputMovieURL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.outputFileType &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; .mov&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.timeRange &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; timeRange&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //export!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exportAsynchronously&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;completionHandler&lt;/span&gt;&lt;span&gt;: { [&lt;/span&gt;&lt;span&gt;weak&lt;/span&gt;&lt;span&gt; exporter] &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    DispatchQueue.main.&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; error &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;failed &lt;/span&gt;&lt;span&gt;\(error.&lt;/span&gt;&lt;span&gt;localizedDescription&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Video saved to &lt;/span&gt;&lt;span&gt;\(outputMovieURL)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;AVKit&lt;/code&gt; and &lt;code&gt;AVFoundation&lt;/code&gt; provide simple objects for manipulating video files. The difficulty when working with video and audio tracks usually comes while providing editing controls for a user. The image below shows how the original video dimensions might change from creation to display.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;different screen dimensions of video during different stages of editing&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1453px) 1453px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1453&quot; height=&quot;716&quot; src=&quot;https://img.ly/_astro/resizing_1kxMMH.webp&quot; srcset=&quot;/_astro/resizing_ZD5fcE.webp 640w, /_astro/resizing_w8gz1.webp 750w, /_astro/resizing_160u1K.webp 828w, /_astro/resizing_1mGc6g.webp 1080w, /_astro/resizing_22qr8E.webp 1280w, /_astro/resizing_1kxMMH.webp 1453w&quot;&gt;&lt;/p&gt;
&lt;p&gt;The original 2160x3840 video appears on an iPhone in a 357x635 frame. Additionally the origin point (green dot) of the video file and the origin point of the UIViews are not equal. Passing the frame of the red, cropping rectangle to an &lt;code&gt;AVComposition&lt;/code&gt; would not work as expected. Inside of the &lt;code&gt;AVComposition&lt;/code&gt; the video resumes it’s 720x1280 dimensions while the 250x250 cropping rectangle would remain 250x250.&lt;/p&gt;
&lt;p&gt;Before using a UIView rectangle with an &lt;code&gt;AVComposition&lt;/code&gt; it needs to resize and the origin point needs to align to the video’s origin. Your app must apply a &lt;code&gt;CGAffineTransform&lt;/code&gt; to reorient the origin points.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;flip and slide rectangle&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1209px) 1209px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1209&quot; height=&quot;793&quot; src=&quot;https://img.ly/_astro/transform-rect_28A6Vy.webp&quot; srcset=&quot;/_astro/transform-rect_ZbJbv8.webp 640w, /_astro/transform-rect_Z25Ixcl.webp 750w, /_astro/transform-rect_Z9DQt7.webp 828w, /_astro/transform-rect_VNLLC.webp 1080w, /_astro/transform-rect_28A6Vy.webp 1209w&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; cropRect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.croppingView.frame &lt;/span&gt;&lt;span&gt;//1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; originFlipTransform &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CGAffineTransform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;scaleX&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;-1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; frameTranslateTransform &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CGAffineTransform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;translationX&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: renderingSize.height)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cropRect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cropRect.&lt;/span&gt;&lt;span&gt;applying&lt;/span&gt;&lt;span&gt;(originFlipTransform) &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cropRect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cropRect.&lt;/span&gt;&lt;span&gt;applying&lt;/span&gt;&lt;span&gt;(frameTranslateTransform) &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you apply regular ratio math to the dimensions of the cropping rectangle to match the video dimensions.&lt;br&gt;
&lt;img alt=&quot;resize rectangle to video dimensions&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 359px) 359px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;359&quot; height=&quot;494&quot; src=&quot;https://img.ly/_astro/translate-resize_54OyH.webp&quot; srcset=&quot;/_astro/translate-resize_54OyH.webp 359w&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; renderingSize &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; playerItem.presentationSize&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; xFactor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; renderingSize.width &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; playerView.bounds.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;.width&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; yFactor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; renderingSize.height &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; playerView.bounds.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;.height&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; newX &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; croppingView.frame.origin.x &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; xFactor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; newW &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; croppingView.frame.width &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; xFactor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; newY &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; croppingView.frame.origin.y &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; yFactor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; newH &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; croppingView.frame.height &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; yFactor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; cropRect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CGRect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;: newX, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: newY, &lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;: newW, &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;: newH)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The transformations above are standard when working with &lt;code&gt;AVKit&lt;/code&gt; and &lt;code&gt;UIKit&lt;/code&gt; or &lt;code&gt;SwiftUI&lt;/code&gt; in an app. The math is not complex, but it is tedious. Unless your app is a custom video editing application, you may find that using a commercial solution such as &lt;a href=&quot;https://img.ly/products/video-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditorSDK&lt;/a&gt; is a better approach. By adding the &lt;a href=&quot;https://img.ly/products/video-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditorSDK&lt;/a&gt;, your app can have professional appearing trim and crop controls as well as filtering, text and more.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;trim crop and other controls&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1131px) 1131px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1131&quot; height=&quot;779&quot; src=&quot;https://img.ly/_astro/crop-trim-and-others_3Iidn.webp&quot; srcset=&quot;/_astro/crop-trim-and-others_Z2jzuUp.webp 640w, /_astro/crop-trim-and-others_1dym89.webp 750w, /_astro/crop-trim-and-others_B0bOy.webp 828w, /_astro/crop-trim-and-others_Z1WfCJ7.webp 1080w, /_astro/crop-trim-and-others_3Iidn.webp 1131w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this article, you saw how to crop a video image and how to trim time from a track, all in Swift. Further, you saw how using an SDK such as &lt;a href=&quot;https://img.ly/products/video-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditorSDK&lt;/a&gt; allows you to provide full-featured video editing for any application.&lt;br&gt;
Looking for more video capabilities? Check out our solution for &lt;a href=&quot;https://img.ly/use-cases/story-reels-short-video-creation&quot;&gt;Short Video Creation&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/products/camera-sdk&quot;&gt;Camera SDK&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading! We hope that you found this tutorial helpful. Feel free to reach out to us on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt; with any questions, comments, or suggestions.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2021/08/resize-swift.jpg" medium="image"/><category>Swift</category><category>Software Development</category><category>Mobile App Development</category><category>Video Editing</category><category>Video Editor</category><category>Code</category><category>Developer Tools</category><category>Tutorial</category><category>App Development</category><category>How-To</category></item><item><title>How to Create Image Filters for iOS</title><link>https://img.ly/blog/how-to-create-image-filters-in-ios/</link><guid isPermaLink="true">https://img.ly/blog/how-to-create-image-filters-in-ios/</guid><description>Write a custom `CIFilter` in Metal and Swift, usable in any CoreImage pipeline.</description><pubDate>Tue, 24 Aug 2021 08:09:37 GMT</pubDate><content:encoded>&lt;p&gt;In this tutorial, you will learn how to write a custom &lt;code&gt;CIFilter&lt;/code&gt; in Metal and Swift. You can use this filter in any CoreImage pipeline.&lt;/p&gt;
&lt;p&gt;Apple offers over 200 image filters in the CoreImage framework, but sometimes you need a little extra to make your images perfect. Apple provides two ways to create customize image filters. You can chain &lt;code&gt;CIFilter&lt;/code&gt;s together in a &lt;code&gt;CIFilter&lt;/code&gt; subclass or wrap a &lt;code&gt;CIKernel&lt;/code&gt; in a &lt;code&gt;CIFilter&lt;/code&gt; subclass. Either method creates a filter that CoreImage can execute on the GPU. That makes the filter fast. CoreImage filters can filter live video without impacting the frame rate. Before iOS 11 the only way to write a custom &lt;code&gt;CIKernel&lt;/code&gt; was to pass a string containing the code to the GPU at runtime using the &lt;a href=&quot;https://www.khronos.org/opengl/wiki/OpenGL_Shading_Language&quot;&gt;OpenGL Shading Language&lt;/a&gt;. This had two drawbacks: it was difficult to find errors in the string of commands and the kernel was not compiled until runtime. This tutorial will show you how to add a custom filter with a Metal-based &lt;code&gt;CIKernel&lt;/code&gt; to any Swift project in Xcode. The code samples in this tutorial use Xcode 12.5 and Swift 5.&lt;/p&gt;
&lt;p&gt;Clone &lt;a href=&quot;https://github.com/waltertyree/core-image-metal&quot;&gt;this repository&lt;/a&gt; for a sample project and example code that supports this tutorial. The repo also contains some other custom filters, demonstrating other aspects of Metal filters.&lt;/p&gt;
&lt;h2 id=&quot;adding-metal-support-to-an-xcode-project&quot;&gt;Adding Metal Support to an Xcode Project&lt;/h2&gt;
&lt;p&gt;The first step is to add a Metal source code file to your project.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;new-file&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 738px) 738px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;738&quot; height=&quot;529&quot; src=&quot;https://img.ly/_astro/new-file_2hEg5C.webp&quot; srcset=&quot;/_astro/new-file_Z1TNBBR.webp 640w, /_astro/new-file_2hEg5C.webp 738w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Find the “Metal File” template, select it and click &lt;code&gt;Next&lt;/code&gt;. Name the file and save it. At compile time, Xcode combines all of the &lt;code&gt;.metal&lt;/code&gt; files in your project into a single &lt;code&gt;.metallib&lt;/code&gt; file. So, organize your Metal code into lots of files or just one file as you prefer. The last step before you start writing code is to add compiler flags for &lt;code&gt;CIKernel&lt;/code&gt; objects.&lt;/p&gt;
&lt;p&gt;In the Build Settings, find the &lt;code&gt;other Metal Compiler Flags&lt;/code&gt; and add an entry of &lt;code&gt;-fcikernel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;build-settings-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 630px) 630px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;630&quot; height=&quot;485&quot; src=&quot;https://img.ly/_astro/build-settings-1_1ww0y8.webp&quot; srcset=&quot;/_astro/build-settings-1_1ww0y8.webp 630w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then find the &lt;code&gt;Other Metal Linker Flags&lt;/code&gt; and add an entry of &lt;code&gt;-cikernel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;other-metal-linker-flag&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 630px) 630px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;630&quot; height=&quot;145&quot; src=&quot;https://img.ly/_astro/other-metal-linker-flag_Z2a5E9F.webp&quot; srcset=&quot;/_astro/other-metal-linker-flag_Z2a5E9F.webp 630w&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Important note:&lt;/em&gt; any project without &lt;code&gt;.metal&lt;/code&gt; files hides the Metal Compiler and Linker sections from build Settings.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-a-metal-file-for-coreimage-kernels&quot;&gt;Setting Up a Metal File for CoreImage Kernels&lt;/h2&gt;
&lt;p&gt;Metal is a general-purpose technology for writing code that will execute on the GPU. The compiler flags tell Metal that you will be writing code to work with &lt;code&gt;CoreImage&lt;/code&gt;. With a Metal source file in your project and the compiler flags set, you’re finally ready to write some code! Open the Metal file you created above. It should be empty except for&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#include &amp;#x3C;metal_stdlib&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;using namespace metal;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Below these lines, import the CoreImage headers by adding:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#include &amp;#x3C;CoreImage/CoreImage.h&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and add a stub for your filter code with:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;extern &quot;C&quot; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  namespace coreimage {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // KERNEL GOES HERE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Prefix your kernel code with &lt;code&gt;extern &quot;C&quot;&lt;/code&gt; and the CoreImage namespace. You write kernel code in the &lt;a href=&quot;https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf&quot;&gt;Metal Shader Language&lt;/a&gt;, which is a variation of C. If you only know Swift, you should be able to rely on Xcode’s code-completion and symbol lookup to help you as you’re learning to write Metal code. In addition to the general language reference, Apple provides a &lt;a href=&quot;https://developer.apple.com/metal/MetalCIKLReference6.pdf&quot;&gt;Metal Shading Language for Core Image Kernels&lt;/a&gt; document.&lt;/p&gt;
&lt;p&gt;The example below creates a &lt;code&gt;CIColorKernel&lt;/code&gt;. This kernel is optimized for changing the color value of each pixel in an image. It will change each pixel to a grayscale version of itself. A filter applying his kernel will send the color value of each pixel of the image to the kernel.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;float4 grayscaleFilterKernel(sample_t s) { //1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  float gray = (s.r + s.g + s.b) / 3;      //2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return float4(gray, gray, gray, s.a);    //3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is how this kernel works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The filter provides the color of the current pixel to the kernel as a &lt;code&gt;sample_t&lt;/code&gt; type. The kernel will return the new color for that pixel as a &lt;code&gt;float4&lt;/code&gt; type. The &lt;code&gt;sample_t&lt;/code&gt; type is equivalent to a &lt;code&gt;float4&lt;/code&gt; type and is only named differently because of historical convention. A &lt;code&gt;float4&lt;/code&gt; type contains four float values. In the case of color, they are the red, green, blue and alpha values for the pixel. Each float has a value between zero and 1.&lt;/li&gt;
&lt;li&gt;Calculate a grayscale version of the pixel by averaging the three color channels.&lt;/li&gt;
&lt;li&gt;Create a new &lt;code&gt;float4&lt;/code&gt; with the averaged value for the three color channels and the original alpha value for the fourth.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;CoreImage also provides a &lt;code&gt;CIWarpKernel&lt;/code&gt; class that is optimized for changing the position of each pixel and a &lt;code&gt;CIBlendKernel&lt;/code&gt; for blending two images. For more complex tasks, CoreImage also provides a general &lt;code&gt;CIKernel&lt;/code&gt;. Apple suggests that chaining together optimized kernels in a pipeline is usually more efficient than trying to write a larger, general kernel. A color-optimized kernel only has access to the color of the current pixel. A transform-optimized kernel can only affect the position of the current pixel. A general kernel can impact color and position as well as read other values from the source image.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-a-cikernel-with-a-cifilter&quot;&gt;Wrapping a `CIKernel` with a `CIFilter`&lt;/h2&gt;
&lt;p&gt;Now you’ll make a &lt;code&gt;CIFilter&lt;/code&gt; subclass as the interface between the Metal code and your Swift code. Every &lt;code&gt;CIFilter&lt;/code&gt; has an &lt;code&gt;outputImage&lt;/code&gt; variable that returns a &lt;code&gt;CIImage&lt;/code&gt;. Generally, input variables for a filter begin with &lt;code&gt;input&lt;/code&gt;. The grayscale filter you are making has a single input and a single output. Create a new Swift file in your project and title it “GrayscaleFilter.swift”. Then be sure to import CoreImage by adding&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import CoreImage&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to the top of the file. Then create the filter as a subclass of &lt;code&gt;CIFilter&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class GrayscaleFilter: CIFilter {&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, create a &lt;code&gt;kernel&lt;/code&gt; variable as either &lt;code&gt;static&lt;/code&gt; or &lt;code&gt;lazy&lt;/code&gt;, so that it only gets created one time, regardless of how often it’s called.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;static var kernel: CIColorKernel = { () -&gt; CIColorKernel in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let url = Bundle.main.url(forResource: &quot;default&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            withExtension: &quot;metallib&quot;)! //1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let data = try! Data(contentsOf: url)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return try! CIColorKernel(functionName: &quot;grayscaleFilterKernel&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                      fromMetalLibraryData: data) //2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Look in the bundle for the compiled metal code. The metal code will have a filename of “default”.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.metalib&lt;/code&gt; may contain many kernels so use the one called “grayscaleFilterKernel”&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now add a variable to hold the input image.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var inputImage: CIImage?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally override the &lt;code&gt;outputImage&lt;/code&gt; variable.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;override var outputImage: CIImage? {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    guard let inputImage = inputImage else { return .none }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return GrayscaleFilter.kernel.apply(extent: inputImage.extent,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                   roiCallback: { (index, rect) -&gt; CGRect in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                                  return rect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                  }, arguments: [inputImage])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;apply&lt;/code&gt; function of the kernel takes an &lt;code&gt;extent&lt;/code&gt; argument. For a &lt;code&gt;CIImage&lt;/code&gt; the &lt;code&gt;extent&lt;/code&gt; property is the width and height of the image. The function has an &lt;code&gt;roiCallback&lt;/code&gt; that allows you to specify what part of the image the kernel uses for each calculation. For a color filter, you generally just pass back the &lt;code&gt;rect&lt;/code&gt;. Finally, pass in any &lt;code&gt;arguments&lt;/code&gt; as an array. Notice that there is not any type checking here. It is up to you to pass in the correct types in the correct order.&lt;/p&gt;
&lt;h2 id=&quot;using-the-filter&quot;&gt;Using the Filter&lt;/h2&gt;
&lt;p&gt;With the Metal code wrapped in a &lt;code&gt;CIFilter&lt;/code&gt; you can now apply the filter images the same as using one of the built-in filters. Create an instance of the filter, then assign the &lt;code&gt;inputImage&lt;/code&gt; variable a &lt;code&gt;CIImage&lt;/code&gt; and display the &lt;code&gt;outputImage&lt;/code&gt;. Your code might look something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let filter = GrayscaleFilter()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter.inputImage = image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;displayView.image = UIImage(ciImage: (filter.outputImage ?? image) ?? CIImage())&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and it can render a grayscale version of an image, like in this example.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;filter-example&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 820px) 820px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;820&quot; height=&quot;766&quot; src=&quot;https://img.ly/_astro/filter-example_Z1xaewd.webp&quot; srcset=&quot;/_astro/filter-example_5m9Hh.webp 640w, /_astro/filter-example_ZL1iK4.webp 750w, /_astro/filter-example_Z1xaewd.webp 820w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;You can write custom filters and chain them to the built-in filters to invent new effects. Many of the math formulas for effects are available on the Internet. Most are easy to translate into the Metal Shader Language to make them perfect for your application. Writing an entire image or video editing application to showcase your filters is a much larger task. Using an SDK like the &lt;a href=&quot;https://img.ly/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;IMG.LY&lt;/a&gt; &lt;code&gt;PhotoEditorSDK&lt;/code&gt; or &lt;code&gt;VideoEditorSDK&lt;/code&gt; can help you provide an app that has the features that users will expect in addition to your cool filters.&lt;/p&gt;
&lt;p&gt;To add your filter to the &lt;code&gt;PhotoEditorSDK&lt;/code&gt; you first wrap your &lt;code&gt;CIFilter&lt;/code&gt; with an &lt;code&gt;Effect&lt;/code&gt;. For a basic filter, you only need to override the &lt;code&gt;newEffectFilter&lt;/code&gt; variable.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import PhotoEditorSDK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class GrayscaleEffect: Effect {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  override var newEffectFilter: CIFilter? {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      GrayscaleFilter()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add the filter to the &lt;code&gt;Effect.all&lt;/code&gt; array when configuring the SDK, using something like&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Effect.all = [NoEffect(), GrayscaleEffect(identifier: &quot;grayscaleFilter&quot;, displayName: &quot;Best Gray&quot;)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now your filter appears the same as the other 60+ filters that ship with the SDK.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 316px) 316px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;316&quot; height=&quot;640&quot; src=&quot;https://img.ly/_astro/create-filter-for-iOS-2_1EN1kE.webp&quot; srcset=&quot;/_astro/create-filter-for-iOS-2_1EN1kE.webp 316w&quot;&gt;&lt;/p&gt;
&lt;p&gt;It applies to live camera previews as well as still images.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this tutorial, you learned how to configure Xcode for custom Metal code. You also learned how to create a color filter kernel in Metal and wrap it in a &lt;code&gt;CIFilter&lt;/code&gt;. By combining your filters with Apple’s filters in pipelines, you can create unique effects for your next app. Further, using an SDK such as &lt;code&gt;PhotoEditorSDK&lt;/code&gt; or &lt;code&gt;VideoEditorSDK&lt;/code&gt; allows you to showcase your filters in a full-featured image or video editing application. The PhotoEditor SDK for iOS &lt;a href=&quot;https://img.ly/docs/pesdk/ios/introduction/overview/&quot;&gt;documentation&lt;/a&gt; will give you deeper look into all other image adjustments including, ofcourse, the image filtering we discussed above.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading! We hope that you found this tutorial helpful. Feel free to reach out to us on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt; with any questions, comments, or suggestions.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2021/08/create-a-photo-filter-for-iOS.jpg" medium="image"/><category>App Development</category><category>iOS App Development</category><category>iOS</category><category>Photo Editing</category><category>Tutorial</category><category>Development</category><category>Mobile App Development</category><category>Photo Filter</category><category>How-To</category></item><item><title>How To Resize an Image in React</title><link>https://img.ly/blog/how-to-resize-an-image-in-react/</link><guid isPermaLink="true">https://img.ly/blog/how-to-resize-an-image-in-react/</guid><description>Quickly resize an image with the `react-image-file-resizer` React library.</description><pubDate>Fri, 30 Jul 2021 10:57:19 GMT</pubDate><content:encoded>&lt;p&gt;In this article, you will see how to resize an image in JavaScript. In particular, here you will learn to achieve this goal with the &lt;code&gt;react-image-file-resizer&lt;/code&gt; React library. If you are looking for a pure Javascript solution, &lt;a href=&quot;https://img.ly/blog/how-to-resize-an-image-with-javascript/&quot;&gt;here’s a quick rundown of the HTML API usage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Providing users with features to resize images has become almost unavoidable. This is because images are larger than ever. It is not a secret that the quality of the images and therefore their file sizes have been increasing for years.&lt;/p&gt;
&lt;p&gt;The problem is that dealing with large files is time-consuming. Plus, it may cost money in bandwidth when uploading or downloading them. This is why it is so important to shrink the size of images by resizing them. Also, these issues fall on end-users, and this should be avoided.&lt;/p&gt;
&lt;p&gt;So, let’s see how to resize an image in React with &lt;code&gt;react-image-file-resizer&lt;/code&gt;. By following this step-by-step tutorial, you will achieve the following &lt;a href=&quot;https://codesandbox.io/s/how-to-resize-an-image-in-react-demo-forked-ip6fi&quot;&gt;result&lt;/a&gt;:&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;This is the list of all the prerequisites for the demo application you are going to build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.npmjs.com/getting-started/&quot;&gt;Node.js and npm 5.2+ and higher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-resize-image&quot;&gt;&lt;code&gt;react-image-file-resizer&lt;/code&gt;&lt;/a&gt; &gt;= 0.4.7&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;resizing-an-image-with-react-image-file-resizer&quot;&gt;Resizing an Image with &lt;code&gt;react-image-file-resizer&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;You can clone the &lt;a href=&quot;https://github.com/imgly/Blog-How-To-Resize-an-Image-in-React&quot;&gt;GitHub repository that supports this article&lt;/a&gt; and try the demo application by launching the following commands:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git clone https://github.com/Tonel/resize-image-react-demo-imgly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cd resize-image-react-demo-imgly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Otherwise, you can continue following this tutorial and build the demo application step by step.&lt;/p&gt;
&lt;h2 id=&quot;1-creating-a-react-project&quot;&gt;1. Creating a React Project&lt;/h2&gt;
&lt;p&gt;Generate an empty working project in React with &lt;a href=&quot;https://create-react-app.dev/docs/getting-started/&quot;&gt;Create React App&lt;/a&gt;, the officially supported way to create single-page React applications. You can initialize a new project called &lt;code&gt;react-image-resizer-demo&lt;/code&gt; with the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npx create-react-app react-image-resizer-demo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will now have a demo project located in the &lt;code&gt;react-image-resizer-demo&lt;/code&gt; folder with the following file structure:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;react-image-resizer-demo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── README.md&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── node_modules&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── package.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── .gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── public&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── favicon.ico&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── index.html&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── logo192.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── logo512.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── manifest.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   └── robots.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── src&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── App.css&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── App.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── App.test.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── index.css&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── index.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── logo.svg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── reportWebVitals.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    └── setupTests.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Move into the &lt;code&gt;react-image-resizer-demo&lt;/code&gt; folder and start a local server by launching these commands:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cd react-image-resizer-demo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reach &lt;a href=&quot;http://localhost:3000/&quot;&gt;http://localhost:3000/&lt;/a&gt; in your browser. You should now be able to see the default Create React App screen, as follows:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The default Create React App screen&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 983px) 983px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;983&quot; height=&quot;728&quot; src=&quot;https://img.ly/_astro/s_D2E103F8349A30F6AA8E27CD2BA4B6EDB946A8DEF5B8272009321B4F51D679F9_1624366125534_image_ZvCahg.webp&quot; srcset=&quot;/_astro/s_D2E103F8349A30F6AA8E27CD2BA4B6EDB946A8DEF5B8272009321B4F51D679F9_1624366125534_image_1hpMyh.webp 640w, /_astro/s_D2E103F8349A30F6AA8E27CD2BA4B6EDB946A8DEF5B8272009321B4F51D679F9_1624366125534_image_Zy6hVM.webp 750w, /_astro/s_D2E103F8349A30F6AA8E27CD2BA4B6EDB946A8DEF5B8272009321B4F51D679F9_1624366125534_image_Z184VNu.webp 828w, /_astro/s_D2E103F8349A30F6AA8E27CD2BA4B6EDB946A8DEF5B8272009321B4F51D679F9_1624366125534_image_ZvCahg.webp 983w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;2-installing-react-image-file-resizer&quot;&gt;2. Installing &lt;code&gt;react-image-file-resizer&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;First, you have to add the &lt;code&gt;react-image-file-resizer&lt;/code&gt; library to your project’s dependencies. You can do it by running the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm install --save react-image-file-resizer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thus, your &lt;code&gt;package.json&lt;/code&gt; file will be updated accordingly. You should now be able to spot &lt;code&gt;react-image-file-resizer&lt;/code&gt; as a dependency.&lt;/p&gt;
&lt;p&gt;Now, all prerequisites have been met. So, you can start building your image resizer component. Let’s see together how.&lt;/p&gt;
&lt;h2 id=&quot;3-building-the-image-resizer-component&quot;&gt;3. Building the Image Resizer Component&lt;/h2&gt;
&lt;p&gt;First, make a &lt;code&gt;components&lt;/code&gt; folder inside &lt;code&gt;src&lt;/code&gt;. Then, create an &lt;code&gt;ImageResizer&lt;/code&gt; folder containing &lt;code&gt;index.js&lt;/code&gt; and &lt;code&gt;index.css&lt;/code&gt;. These two files will contain the resizer component definition and style respectively.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://paper-attachments.dropbox.com/s_0C9FF6F84F9D5BBDC6112B53688A82A0D901A542FB0DD0CC04FE4E4B7D0590ED_1626875270825_image.png&quot; alt=&quot;The components folder&quot;&gt;&lt;/p&gt;
&lt;p&gt;Initialize &lt;code&gt;index.js&lt;/code&gt; with the following snippet:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; React &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; ImageResizer&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;{&lt;/span&gt;&lt;span&gt;/*TODO*/&lt;/span&gt;&lt;span&gt;}&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; ImageResizer;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, you have just defined an empty &lt;code&gt;ImageResizer&lt;/code&gt; component.&lt;br&gt;
Now, import the &lt;code&gt;Resizer&lt;/code&gt; utility from the &lt;code&gt;react-image-file-resizer&lt;/code&gt; library. Add it to the &lt;code&gt;ImageResizer&lt;/code&gt; imports:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import Resizer from &apos;react-image-file-resizer&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is what the final &lt;code&gt;ImageResizer&lt;/code&gt; will look like:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import React, {useEffect, useState} from &quot;react&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import Resizer from &quot;react-image-file-resizer&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function ImageResize(props) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const {imageToResize, onImageResized, resizeAspect, resizeQuality} = props;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const [imageToResizeUri, setImageToResizeUri] = useState();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const [imageToResizeWidth, setImageToResizeWidth] = useState();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const [imageToResizeHeight, setImageToResizeHeight] = useState();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    useEffect(() =&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if (imageToResize) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            const reader = new FileReader();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            reader.addEventListener(&apos;load&apos;, () =&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                setImageToResizeUri(reader.result);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            reader.readAsDataURL(imageToResize);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }, [imageToResize])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    useEffect(() =&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        if (imageToResize &amp;#x26;&amp;#x26; imageToResizeWidth &amp;#x26;&amp;#x26; imageToResizeHeight) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            Resizer.imageFileResizer(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                imageToResize,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                imageToResizeWidth * resizeAspect,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                imageToResizeWidth * resizeAspect,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                &quot;JPEG&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                resizeQuality,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                (uri) =&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    onImageResized(uri)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                &quot;base64&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }, [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        imageToResize, imageToResizeWidth, imageToResizeHeight,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        onImageResized, resizeAspect, resizeQuality&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;img&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            src={imageToResizeUri}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            onLoad= {(e) =&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                const img = e.target;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                setImageToResizeWidth(img.width);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                setImageToResizeHeight(img.height);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            crossorigin=&quot;anonymous&quot; // to avoid CORS-related problems&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ImageResize.defaultProps = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    onImageResized: () =&gt; {},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    resizeAspect: 0.5,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    resizeQuality: 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export default ImageResize;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;imageToResize&lt;/code&gt; is the source &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Blob&quot;&gt;Blob&lt;/a&gt; representing the image received from the props. First, &lt;code&gt;imageToResize&lt;/code&gt; is transformed into a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Base64&quot;&gt;&lt;code&gt;Base64&lt;/code&gt;&lt;/a&gt; URI by the first &lt;a href=&quot;https://legacy.reactjs.org/docs/hooks-effect.html&quot;&gt;&lt;code&gt;useEffect()&lt;/code&gt;&lt;/a&gt; function, and then showed. After being loaded by the &lt;code&gt;&amp;#x3C;img&gt;&lt;/code&gt; HTML tag, the image’s width and height are saved to be used during the resizing operation. This is done by the second &lt;code&gt;useEffect()&lt;/code&gt; function, which takes care of resizing the Blob image received by employing the &lt;code&gt;Resizer&lt;/code&gt; utility.&lt;/p&gt;
&lt;p&gt;Please, note that &lt;code&gt;resizeAspect&lt;/code&gt; and &lt;code&gt;resizeQuality&lt;/code&gt; are two props in charge of defining the resize aspect and quality percentage, respectively. By default, they are assigned to 0.5 and 100. This means that the resized image will be 50% smaller and no compression will be applied during the resizing process.&lt;/p&gt;
&lt;h2 id=&quot;4-putting-it-all-together&quot;&gt;4. Putting It All Together&lt;/h2&gt;
&lt;p&gt;Now, let’s see the &lt;code&gt;ImageResizer&lt;/code&gt; component defined above in action. All you need to do, is change the &lt;code&gt;App.js&lt;/code&gt; file as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; React, { useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;./App.css&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; ImageResize &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./components/ImageResizer&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; App&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;imageToResize&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setImageToResize&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;resizedImage&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setResizedImage&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; onUploadFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (event.target.files &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; event.target.files.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &gt;&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      setImageToResize&lt;/span&gt;&lt;span&gt;(event.target.files[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;app&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&gt;Image Resizer&amp;#x3C;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        Please, upload an image and it will be showed both original and resized&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        by 50%&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;file&quot;&lt;/span&gt;&lt;span&gt; accept&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;image/*&quot;&lt;/span&gt;&lt;span&gt; onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{onUploadFile} /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;ImageResize&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          imageToResize&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{imageToResize}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          onImageResized&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{(&lt;/span&gt;&lt;span&gt;resizedImage&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; setResizedImage&lt;/span&gt;&lt;span&gt;(resizedImage)}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      {resizedImage &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&gt;Resized Image&amp;#x3C;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Resize Image&quot;&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{resizedImage} /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      )}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; App;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;input&lt;/code&gt; element allows users to upload an image. This is stored in the &lt;code&gt;imageToResize&lt;/code&gt; state variable, and then passed to &lt;code&gt;ImageResizer&lt;/code&gt; as a props. This will take care of resizing it accordingly. The resulting resized image is saved thanks to the &lt;code&gt;setResizedImage&lt;/code&gt; function, and finally displayed in the &lt;em&gt;Resize Image&lt;/em&gt; section.&lt;/p&gt;
&lt;p&gt;If you are a Next.js user, you can use the &lt;a href=&quot;https://writech.run/blog/how-to-make-next-js-image-optimization-work-on-aws-elastic-beanstalk-2776ea255eff/&quot;&gt;&lt;code&gt;&amp;#x3C;Image /&gt;&lt;/code&gt; component to make it resize images for you&lt;/a&gt;. React will then render resized images automatically.&lt;/p&gt;
&lt;h2 id=&quot;final-considerations-on-react-image-file-resizer&quot;&gt;Final Considerations on &lt;code&gt;react-image-file-resizer&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Resizing an image cannot be considered a complex task. However, using a library like &lt;code&gt;react-image-file-resizer&lt;/code&gt; makes everything easier. In fact, you can achieve your goal with just a handful of lines of code. On the other hand, you should take into account what using such specific libraries implies. In fact, when you need to perform many image-related operations, you may be ending up with as many libraries as operations required.&lt;/p&gt;
&lt;p&gt;Not only might they be complex to make them coexist, but they may have very different UIs as well. This is why, harnessing a commercial and more complete solution such as &lt;a href=&quot;https://img.ly/products/photo-sdk&quot;&gt;&lt;code&gt;PhotoEditorSDK&lt;/code&gt;&lt;/a&gt; could be a better approach. With only one library, you would get several tools to deal with images as you need. This, while preserving the consistency of your application’s UI. Plus, whenever you need help, you can ask for support from the &lt;a href=&quot;https://img.ly/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;img.ly&lt;/a&gt; developers who built the SDK.&lt;/p&gt;
&lt;h2 id=&quot;resizing-an-image-with-photoeditorsdk&quot;&gt;Resizing an Image with PhotoEditorSDK&lt;/h2&gt;
&lt;p&gt;First, you should read &lt;a href=&quot;https://img.ly/docs/pesdk/web/introduction/getting_started/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;this&lt;/a&gt; article from &lt;a href=&quot;https://img.ly/docs/pesdk/guides/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;the official documentation&lt;/a&gt; on how to get started with &lt;code&gt;PhotoEditorSDK&lt;/code&gt; in React. Then, by using the &lt;a href=&quot;https://img.ly/docs/pesdk/web/features/transform/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;transform tool&lt;/a&gt; you can perform cropping, &lt;a href=&quot;https://img.ly/docs/pesdk/web/features/transform/#image-resizing?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;resizing&lt;/a&gt;, flipping, and rotation operations with just one feature. This way, you should be able to achieve the desired result&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;resize-image-with-pesdk-react&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1095px) 1095px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1095&quot; height=&quot;652&quot; src=&quot;https://img.ly/_astro/resize-image-with-pesdk-react_Z2rpOL5.webp&quot; srcset=&quot;/_astro/resize-image-with-pesdk-react_Z25Tfyt.webp 640w, /_astro/resize-image-with-pesdk-react_21L4Vs.webp 750w, /_astro/resize-image-with-pesdk-react_Zk4j8T.webp 828w, /_astro/resize-image-with-pesdk-react_WzXl1.webp 1080w, /_astro/resize-image-with-pesdk-react_Z2rpOL5.webp 1095w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this article, we looked at how to resize an image in React. Although this cannot be considered a complex feature to implement, using a library such as &lt;code&gt;react-image-file-resizer&lt;/code&gt; is recommended. As we have seen, you can resize an image effortlessly and with only a few lines of code. On the other hand, it is a library with a very specific and limited purpose. So, if you needed to perform more than one operation on your images, you might want to take advantage of a more advanced and complete solution – such as &lt;code&gt;PhotoEditorSDK&lt;/code&gt;.&lt;/p&gt;</content:encoded><dc:creator>Antonello</dc:creator><media:content url="https://blog.img.ly/2021/07/resize-image-with-react-1.png" medium="image"/><category>React</category><category>App Development</category><category>Mobile App Development</category><category>Photo Editing</category><category>Tutorial</category><category>Tech</category><category>Developer</category><category>Code</category><category>Developer Tools</category><category>React Native</category><category>Software Development</category><category>How-To</category></item><item><title>A Photo and Video Editor for React Native Apps</title><link>https://img.ly/blog/a-photo-and-video-editor-for-your-react-native-apps/</link><guid isPermaLink="true">https://img.ly/blog/a-photo-and-video-editor-for-your-react-native-apps/</guid><pubDate>Fri, 29 May 2020 10:30:01 GMT</pubDate><content:encoded>&lt;hr&gt;
&lt;h3 id=&quot;how-to-integrate-a-photo-and-video-editor-into-your-react-native-app&quot;&gt;How to integrate a Photo and Video Editor into your React Native App&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;This tutorial walks you through the integration process of PhotoEditor SDK and VideoEditor SDK into your React Native app for iOS and Android. You’ll learn how to use our React Native modules to facilitate the integration and to customize our editors. For this tutorial we presume that all the necessary development tools for building an iOS and Android app are met, so make sure to complete the official &lt;a href=&quot;https://reactnative.dev/docs/getting-started&quot;&gt;React Native CLI Quickstart&lt;/a&gt; guides for iOS and Android beforehand.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Please make sure to acquire the licenses for &lt;a href=&quot;https://img.ly/products/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; and &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;VideoEditor SDK&lt;/a&gt; before integrating them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/embed/e9JiCMQKrJY?feature=oembed&quot;&gt;https://www.youtube.com/embed/e9JiCMQKrJY?feature=oembed&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In this tutorial, we’re going to show how to integrate PhotoEditor SDK and VideoEditor SDK for iOS and Android into your React Native app. Therefore, we created React Native modules for our products to simplify this process for you as much as possible. We’re going to use &lt;a href=&quot;https://github.com/imgly/vesdk-react-native&quot;&gt;VideoEditor SDK’s README&lt;/a&gt;, which is in most parts identical to the PhotoEditor SDK’s README, and Visual Studio Code. So, let’s get started.&lt;/p&gt;
&lt;p&gt;First, we create a React Native project with the name “Demo” based on the default template by using the command &lt;code&gt;npx react-native init Demo&lt;/code&gt;. The project will now be initialized and automatically install all dependencies of the current React Native version. Afterwards, we can find the new React Native project ready to use in the folder “Demo”. So, we’ll speed this up a little.&lt;/p&gt;
&lt;p&gt;We already prepared another folder with resources and assets that we want to integrate in our app. Here we chose an image, the required licenses for our PhotoEditor SDK and VideoEditor SDK for both target platforms, a video and two logos that we will later use to show how we can customize our editors. We copy these resources into the root of our project to make the resources accessible for our app.&lt;/p&gt;
&lt;p&gt;Now, we switch to the folder of the “Demo” project*.* We can now copy and execute the command &lt;code&gt;yarn add react-native-videoeditorsdk&lt;/code&gt; from the README to install the dependencies to the React Native module for our VideoEditor SDK … and to the React Native module for PhotoEditor SDK by issuing the command &lt;code&gt;yarn add react-native-photoeditorsdk&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, we’re going to set up the dependencies for our native iOS libraries. We can simply copy the command &lt;code&gt;cd ios &amp;#x26;&amp;#x26; pod install &amp;#x26;&amp;#x26; cd ..&lt;/code&gt; from the README and execute it to install all iOS dependencies. They include the native PhotoEditor SDK and VideoEditor SDK libraries that are required by our React Native modules.&lt;/p&gt;
&lt;p&gt;And now, we set up the dependencies for our native Android libraries. The required steps that we will now take are described in detail in the README.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;android {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  defaultConfig {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    multiDexEnabled true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dependencies {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  implementation ‘androidx.multidex:multidex:2.0.1’&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We copy the lines and add them at the end of our &lt;code&gt;android/app/build.gradle&lt;/code&gt; file. Now we need to change the superclass of our &lt;code&gt;MainApplication&lt;/code&gt; class to enable Multidex. Next, we add the img.ly repository and the plugin by copying the following lines and add them at the top of our &lt;code&gt;android/build.gradle&lt;/code&gt; file located in our Android folder.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;buildscript {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  repositories {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    jcenter()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    maven { url &quot;https://plugins.gradle.org/m2/&quot; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    maven { url &quot;https://artifactory.img.ly/artifactory/imgly&quot; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  dependencies {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    classpath &quot;org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    classpath &apos;ly.img.android.sdk:plugin:7.1.8&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we can configure our PhotoEditor SDK and VideoEditor SDK by opening the &lt;code&gt;android/app/build.gradle&lt;/code&gt; file and add these lines under &lt;code&gt;apply plugin: “com.android.application&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;apply plugin: &apos;ly.img.android.sdk&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;apply plugin: &apos;kotlin-android&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Comment out the modules you don&apos;t need, to save size.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;imglyConfig {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  modules {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;ui:text&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;ui:focus&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;ui:frame&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;ui:brush&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;ui:filter&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;ui:sticker&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;ui:overlay&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;ui:transform&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;ui:adjustment&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;ui:text-design&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;ui:video-trim&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // This module is big, remove the serializer if you don&apos;t need that feature.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;backend:serializer&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Remove the asset packs you don&apos;t need, these are also big in size.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;assets:font-basic&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;assets:frame-basic&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;assets:filter-basic&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;assets:overlay-basic&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;assets:sticker-shapes&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    include &apos;assets:sticker-emoticons&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Getting back to our iOS version, we can now launch our demo project on iOS, which will currently look like a plain React Native project that we initialized with the first command.&lt;/p&gt;
&lt;p&gt;The main difference to an off-the-shelf React Native project is that our React Native modules are installed and ready-to-use in the &lt;code&gt;App.js&lt;/code&gt; file once the native projects are compiled. Then, it won’t be necessary to recompile the native projects for the remainder of this tutorial. We sped up the compilation a little and here we go — our React Native app is running on the iOS simulator. Now, we do the same for the Android version and wait until the project is compiled.&lt;/p&gt;
&lt;p&gt;Now, the demo project launched on both platforms as we can see on the right on the iOS simulator at the top, and on the Android emulator at the bottom of the screen.&lt;/p&gt;
&lt;p&gt;We’ve decided that we want to start our photo editor by pressing a button. So next, we’re going to actually customize our React Native app by adding this button. Therefore, we open the &lt;code&gt;App.js&lt;/code&gt; file and import the &lt;code&gt;Button&lt;/code&gt; component in order to create a button with the title “Edit a sample image”. For now, we leave the &lt;code&gt;onPress&lt;/code&gt; function empty.&lt;/p&gt;
&lt;p&gt;We save the &lt;code&gt;App.js&lt;/code&gt; file to trigger a refresh of the running apps and immediately see the result on the right. The new button appears in both, the iOS and the Android app.&lt;/p&gt;
&lt;p&gt;Now we create a second button with the title “Edit a sample video”. This will respectively start our video editor. And again, we save the &lt;code&gt;App.js&lt;/code&gt; file and see the second button appear on the right side.&lt;/p&gt;
&lt;p&gt;Next, we are going to add the code that actually opens our editors when we press the buttons. Visual Studio Code automatically imported the respective React Native PhotoEditor SDK module for us at the very top of the file, while writing the code that makes use of our SDK. We do the same for the VideoEditor SDK. We use the &lt;code&gt;require&lt;/code&gt; function to make static assets available to our app. Here, we “require” our sample image and our sample video that we copied to the app’s folder in the beginning and pass them as the first argument to our &lt;code&gt;openEditor&lt;/code&gt; functions. The first argument can also be a regular URI.&lt;/p&gt;
&lt;p&gt;We save the &lt;code&gt;App.js&lt;/code&gt; file again and now we can click the buttons to start our photo editor or video editor. There we go! We still see a watermark here. The reason for this watermark is that we haven’t unlocked our SDKs so far which we will do next.&lt;/p&gt;
&lt;p&gt;We unlock both products with our licenses to get rid of the watermark. If not unlocked, the watermark will be on both the image and video previews as well as on the exported images and videos. To unlock the products, we use the &lt;code&gt;unlockWithLicense&lt;/code&gt; function of each SDK. In total, we need four license files, one license file for each product and platform combination. The license files should be named &lt;code&gt;pesdk_license&lt;/code&gt; and &lt;code&gt;vesdk_license&lt;/code&gt; with platform-specific extensions &lt;code&gt;.ios.json&lt;/code&gt; and &lt;code&gt;.android.json&lt;/code&gt;. React Native will then automatically pick the right file for the corresponding platform. After this, the watermarks will be removed for PhotoEditor SDK and VideoEditor SDK on both platforms. And now you can also see it in the simulator —  no watermarks anymore.&lt;/p&gt;
&lt;p&gt;In the next step, we’re going to change the configuration of the editors. If no changes are made to the configuration, our default stickers are available with the editor. To customize them, we need to import the &lt;code&gt;Configuration&lt;/code&gt; from either the PhotoEditor SDK or VideoEditor SDK. The configurations are compatible between both products. So, for this tutorial, we decided to use the VideoEditor SDK configuration which we are now using by adding &lt;code&gt;Configuration&lt;/code&gt; to the &lt;code&gt;react-native-videoeditorsdk&lt;/code&gt; imports. We decided that we want to add custom stickers to our editors. Therefore we define a non-default configuration to the sticker tool.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const configuration: Configuration = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  sticker: {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To customize the sticker assets, we need to define the sticker “categories” array. Here, we define a new category and name the identifier &lt;code&gt;demo_sticker_category&lt;/code&gt;. These asset identifiers must always be unique. Next, we set a name for the category and we name it “Logos”.&lt;/p&gt;
&lt;p&gt;Each category also requires a thumbnail image to be displayed in the editor. For the thumbnail, we use the React logo that we added to the folder of our app at the very beginning. Next, we define the items for this new sticker category. These items are the actual stickers that we can apply to the edited image or video. We now create a new sticker for the React logo. Therefore, we call the identifier &lt;code&gt;demo_sticker_react&lt;/code&gt; and name it “React”. These sticker names won’t appear in the UI, but they are used for accessibility. Now, we need to define the actual image that should be used for that sticker. Here we use the React image again.&lt;/p&gt;
&lt;p&gt;To create a second sticker, we can now copy and paste the code of the first sticker. We create a sticker with our img.ly logo and rename the identifier of the pasted code to &lt;code&gt;demo_sticker_imgly&lt;/code&gt;. Accordingly, we set the name to “img.ly” and change the file to &lt;code&gt;imgly.png&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In addition, we want to specify a non-default tint mode for our second sticker by using &lt;code&gt;tintMode: TintMode.SOLID&lt;/code&gt; which enables us to change the color of the sticker. The &lt;code&gt;TintMode&lt;/code&gt; type is automatically added to the VideoEditor SDK imports for us by Visual Studio Code. Now, that we completed our configuration, we need to pass it as the second argument to the &lt;code&gt;openEditor&lt;/code&gt; functions in order to take effect.&lt;/p&gt;
&lt;p&gt;We save the &lt;code&gt;App.js&lt;/code&gt; file again to refresh the running apps and we can see the result live after starting a new editing session. Please note that you cannot alter the configuration of a running editor instance. You always need to start a new editing session to see configuration changes.&lt;/p&gt;
&lt;p&gt;We want to use another feature of our SDKs which is called serialization. With the serialization feature, we can capture all image and video editing operations that are applied in the editor and export them. This allows us to import the editing operations in later sessions and continue editing. The serializations are compatible between both products as well. The input serialization is the third parameter of the &lt;code&gt;openEditor&lt;/code&gt; functions of our SDKs and the output serialization is optionally part of their result type.&lt;/p&gt;
&lt;p&gt;First, we check if the result is “null”. This is the case when a user clicks the “Discard” button in the editor and thus does not export an image or video. If the result is not ”null”, we know that the user exported an image or video. Then we can assign the exported serialization to the previously defined global serialization variable which will then be input to the next editing session. We copy the code and add it to the video editor as well to enable the serialization function here too.&lt;/p&gt;
&lt;p&gt;Now, one thing is left to enable the actual serialization export in the configuration. The serialization export is disabled per default because not every user needs the serialization feature. Here, we enable it now and also change the export type to &lt;code&gt;object&lt;/code&gt;. By doing so, the result type of the editor will contain the serialization as an &lt;code&gt;object&lt;/code&gt;. Per default, the serialization is exported to a file and that file name is returned as part of the export result. Writing the serialization to a file is a reasonable default as serializations can be quite large, especially if large amounts of binary data for personal stickers are embedded.&lt;/p&gt;
&lt;p&gt;Now, we can run the app on the simulator and use all the parameters that we configured in this tutorial. First, we can add our custom stickers, both the React logo and the img.ly logo. Here we can also change the colors which we enabled with the tint mode.&lt;/p&gt;
&lt;p&gt;We can also use the text design tool to add a phrase to our image. Here we can pick different designs, so we’re trying a couple and place the text design fitting to the image and logo.&lt;/p&gt;
&lt;p&gt;Next, we export our image with the serialization. With the serialization function enabled, it is now possible to import the editing operations into our video editor. This allows us to keep on editing because the serialization is compatible between both products. So here we can add further words to our text design. We can also put filters on our video. For example, we can choose the peach duo tone and increase the contrast a little. And here we go! We successfully integrated PhotoEditor SDK and VideoEditor SDK into our React Native app.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading! To stay in the loop, subscribe to our&lt;/strong&gt; &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;&lt;strong&gt;Newsletter&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Julia</dc:creator><dc:creator>Alexander</dc:creator><media:content url="https://blog.img.ly/2020/05/_Integration_--1-.png" medium="image"/><category>App Development</category><category>Code</category><category>Developer Tools</category><category>Developer</category><category>Development</category><category>Image Editing</category><category>JavaScript</category><category>Mobile App Development</category><category>Photo Editing</category><category>React</category><category>React Native</category><category>Software Development</category><category>Tech</category><category>Tutorial</category><category>Video Editing</category><category>How-To</category><category>Company</category></item><item><title>Case Study: W | Bear &amp; PhotoEditor SDK</title><link>https://img.ly/blog/case-study-w-bear-photoeditor-sdk-10f06eb8cb14/</link><guid isPermaLink="true">https://img.ly/blog/case-study-w-bear-photoeditor-sdk-10f06eb8cb14/</guid><description>Building a social environment for the LGBT community</description><pubDate>Mon, 17 Sep 2018 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://wbear.lgbt/?utm_source=Case%20Study%20PhotoEditor%20SDK&quot;&gt;W | Bear&lt;/a&gt; is the first global photo and video blogging social community for gay men. The free app allows its users to post pictures and videos to a personal feed, express their creativity and connect with like-minded bears. The blogging community provides a safe place where people can build and shape a social environment, chat, flirt and date. W | Bear was created and developed by the private and public cloud solution provider gNetLabs. We sat together with Xavier Nicolle, CEO at gNetLabs to talk about the vision behind W | Bear, time to market and providing users with tailored assets to amplify engagement.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;“W | Bear is aimed at the bear subculture of the LGBT community. People express themselves, their story and their life through photos and videos. Our users are very happy with the app. 18 months after the release we reached around 150.000 users worldwide, which is beyond our expectation and it’s getting faster and faster. We get a lot of positive reactions and amazing feedback from users that tell us, that our application helped them to gain more self-esteem and to better integrate into the community,” Xavier Nicolle says.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 300px) 300px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;300&quot; height=&quot;533&quot; src=&quot;https://img.ly/_astro/1-vQ6biYxXWoVjVrO12DT4iA_Z2rduG2.webp&quot; srcset=&quot;/_astro/1-vQ6biYxXWoVjVrO12DT4iA_Z2rduG2.webp 300w&quot;&gt;&lt;/p&gt;
&lt;p&gt;As pictures were going to be a vital part of the Instagram like photo blogging application (to this day, more than 2 million pictures and videos have been posted on the app), the folks at gNetLabs started exploring options as their CEO explains: “Time to market was critical for us, and we were kind of in a rush to develop the app. From our background as a hosting company, we’d usually develop and host everything ourselves, however, due to time problems, that wasn’t a viable option regarding the photo editing functionalities. So, we jumped into &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; to save time, really liked it and eventually kept it. The features and pricing perfectly fit our needs, the integration was straightforward, and the SDK is of great quality and open enough for fine-tuning. On top of that, unlike Adobe Creative SDK for example, it is not linked to any other service. And that is exactly what we were looking for, a piece of software that we could simply plug in that isn’t linked to a service that we don’t need.”&lt;/p&gt;
&lt;p&gt;To offer the best experience possible with &lt;a href=&quot;https://www.wbear.lgbt/?utm_source=Case%20Study%20PhotoEditor%20SDK&quot;&gt;W | Bear&lt;/a&gt;, the folks at gNetLabs pay close attention to how people engage with the app and its features. “Our users mostly work with stickers, frames, and overlays when editing their pictures,” Xavier Nicolle says, “so, bearing that in mind, we want to provide more content and assets that are tailored for the community so our users will hopefully make more use of the editor. Currently, we’re working together with artists that develop stickers that we’re going to introduce using the SDK.”&lt;/p&gt;
&lt;p&gt;But it doesn’t stop with W | Bear, as Xavier Nicolle describes the vision behind the community: “W | Bear is the first piece of a larger social network we want to develop that is dedicated and aimed at the whole LGBT community. We’re going to release more applications using the same technology as W | Bear to address more subcultures. On top of that, there are going to be corresponding websites for each app, so the user will be able to find the same functionalities and tools across all platforms.”&lt;/p&gt;
&lt;p&gt;*&lt;strong&gt;*Thanks for reading! To stay in the loop, subscribe to our&lt;/strong&gt; &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;&lt;strong&gt;Newsletter&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.**&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Felix</dc:creator><media:content url="https://blog.img.ly/2020/04/image-34.png" medium="image"/><category>Photography</category><category>Social Media</category><category>LGBTQ</category><category>Case Study</category><category>Mobile App Development</category><category>Case Studies</category></item><item><title>How to build Instagram’s Story Editor in a Day</title><link>https://img.ly/blog/how-to-build-instagrams-story-editor-in-a-day-23be9adff9b/</link><guid isPermaLink="true">https://img.ly/blog/how-to-build-instagrams-story-editor-in-a-day-23be9adff9b/</guid><pubDate>Wed, 13 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A common question we get asked from our customers, is whether they’ll be able to create an entirely different user interface using our &lt;a href=&quot;https://img.ly/products/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt;. The SDK comes with it’s own customizable UI, but customization are of course limited to a certain extent. As our SDK is used in many different use cases and contexts, we like to explore what’s possible with the PhotoEditor SDK and its included components. Here, &lt;strong&gt;we decided to build a UI similar to Instagram’s Stories or Snapchat using our SDK&lt;/strong&gt;. By now, this ‘Story UI’ has become a popular way of quickly designing with different elements (stickers, brush and text) rather than enhancing and styling the image only.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Our default UI vs Story UI&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 800px) 800px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;800&quot; height=&quot;590&quot; src=&quot;https://img.ly/_astro/1-Rv39RvsHUCI4TXKC1rHXZQ_Z1l5n2b.webp&quot; srcset=&quot;/_astro/1-Rv39RvsHUCI4TXKC1rHXZQ_Z2eFP2.webp 640w, /_astro/1-Rv39RvsHUCI4TXKC1rHXZQ_1qqAh8.webp 750w, /_astro/1-Rv39RvsHUCI4TXKC1rHXZQ_Z1l5n2b.webp 800w&quot;&gt;&lt;/p&gt;
&lt;p&gt;So we grabbed our own &lt;a href=&quot;https://img.ly/docs/pesdk/&quot;&gt;docs&lt;/a&gt; and headed out to create a demo in which we recreate the Instagram Stories using components from the PhotoEditor SDK. This article presents our approach by starting with a general overview and then diving into the different view controllers to look at specific implementation details. You can follow along by downloading the accompanying &lt;a href=&quot;https://github.com/imgly/pesdk-blog-instagram-ui&quot;&gt;code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Our customized Story UI in action.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 290px) 290px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;290&quot; height=&quot;580&quot; src=&quot;https://img.ly/_astro/1-s-ZFgd4EuUcuy_s4MDz2sQ_Z9PmrR.webp&quot; srcset=&quot;/_astro/1-s-ZFgd4EuUcuy_s4MDz2sQ_Z9PmrR.webp 290w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;architecture-andoverview&quot;&gt;Architecture and Overview&lt;/h2&gt;
&lt;p&gt;Any image editing interface is naturally centered around some sort of canvas or preview. This is where the image with all operations applied is rendered and any changes are shown to the user. All tools add their own interface elements to allow modifications like adding stickers, text or brush strokes to the image. To create such a hierarchy of tools, the &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; makes heavy use of an iOS pattern called &lt;em&gt;view controller containment&lt;/em&gt;. The root view controller, an &lt;code&gt;EditViewController&lt;/code&gt; in this case, manages a series of child view controllers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;PhotoEditPreviewController&lt;/code&gt; that handles the internal model and all rendering, as well as the rendering canvas itself&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;TextSpriteEditController&lt;/code&gt; above the &lt;code&gt;PhotoEditPreviewController&lt;/code&gt; that allows selection and manipulation of text sprites&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;StickerSpriteEditController&lt;/code&gt; above the &lt;code&gt;PhotoEditPreviewController&lt;/code&gt;manages selection and manipulation of stickers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/max/1024/1*iNnmjNai7mPywoKsgLz-xg.png&quot; alt=&quot;The view hierarchy within the EditViewController. The user facing controls are on the left, the root window is on the right.&quot;&gt;&lt;/p&gt;
&lt;p&gt;The child view controllers may manage additional view controllers themselves, but we only need to manage the topmost objects and wire their interfaces together. This is done within the &lt;code&gt;EditViewController&lt;/code&gt; who is also responsible for presenting the view controllers that implement the different tools, &lt;code&gt;StickerViewController&lt;/code&gt;, &lt;code&gt;BrushViewController&lt;/code&gt; and &lt;code&gt;TextViewController&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These are mainly responsible for managing the interface and adjusting the model accordingly, while the ‘real’ work is being done in the background by the &lt;code&gt;PhotoEditPreviewController&lt;/code&gt; whenever the model gets updated. Thanks to the SDK, creating our &lt;code&gt;BrushViewController&lt;/code&gt; boils down to creating and wiring a &lt;code&gt;BrushEditController&lt;/code&gt; and adding its view, color and size controls to the &lt;code&gt;BrushViewControllers&lt;/code&gt; view. This creates a fully fledged brush tool with OpenGL rendering, color and brush size adjustments, and great performance in just &lt;strong&gt;89 lines&lt;/strong&gt; of code.&lt;/p&gt;
&lt;h2 id=&quot;editviewcontroller&quot;&gt;EditViewController&lt;/h2&gt;
&lt;p&gt;As described in the previous section, the &lt;code&gt;EditViewController&lt;/code&gt; is the root view controller of our demo application and is responsible for showing a preview, presenting tools and rendering the final output. To do so, we add a &lt;code&gt;PreviewEditViewController&lt;/code&gt; and &lt;code&gt;SpriteEditControllers&lt;/code&gt; to the root view and register the &lt;code&gt;EditViewController&lt;/code&gt; as their delegate. Registering as a delegate, allows the &lt;code&gt;EditViewController&lt;/code&gt; to wire all child view controllers and pass data between them. In order to do so, we just need to return objects of other view controllers or react to model changes. All controllers then use the &lt;code&gt;EditViewController&lt;/code&gt; to ask for objects or data they need, for example the current size of the preview view, in order to rearrange interface elements or calculate model updates. As an example, the &lt;code&gt;spriteEditControllerPreviewView(_ spriteEditController:)&lt;/code&gt; method of the &lt;code&gt;SpriteEditControllerDelegate&lt;/code&gt; protocol, asks for the current preview view. All we need to do in our &lt;code&gt;EditViewController&lt;/code&gt; is to get this view from our &lt;code&gt;PreviewEditViewController&lt;/code&gt; and return it:&lt;/p&gt;

&lt;h3 id=&quot;sprite-handling&quot;&gt;Sprite Handling&lt;/h3&gt;
&lt;p&gt;Stickers and text sprites are both sprites, but there are different &lt;code&gt;SpriteEditController&lt;/code&gt; subclasses for each sprite type, because of differing gestures and UI elements. The Instagram UI allows the editing of both sticker and text sprites within a single view, so we need to add both specific controllers, &lt;code&gt;StickerSprite&lt;/code&gt;- and &lt;code&gt;TextSpriteEditController&lt;/code&gt; to our &lt;code&gt;EditViewController&lt;/code&gt; and dynamically enable the appropriate one depending on the currently selected sprite.&lt;/p&gt;
&lt;h2 id=&quot;stickerviewcontroller&quot;&gt;StickerViewController&lt;/h2&gt;
&lt;p&gt;For adding stickers, the Instagram Stories UI offers a single button that presents a grid of all available stickers. Replicating this using the PhotoEditor SDK is easy to do, as the SDK uses a collection view for presentation as well. This collection view is embedded in a &lt;code&gt;StickerSelectionController&lt;/code&gt; which we add to our &lt;code&gt;StickerViewController&lt;/code&gt;. Once again, after registering as the &lt;code&gt;StickerSelectionController&lt;/code&gt;s delegate, we get notified upon selection of any sticker and just need to change the &lt;code&gt;PhotoEditModel&lt;/code&gt; accordingly. To do so, we create a link between the root and the currently presented view controller using the &lt;code&gt;StickerViewControllerDelegate&lt;/code&gt; protocol. Once a sticker has been selected, we create or update a &lt;code&gt;StickerSpriteModel&lt;/code&gt;, pass it to the &lt;code&gt;EditViewController&lt;/code&gt;, which updates the &lt;code&gt;PreviewEditViewController&lt;/code&gt; model:&lt;/p&gt;

&lt;p&gt;This triggers a new rendering pass and the sticker appears on screen. All that’s left to do now is closing the &lt;code&gt;StickerViewController&lt;/code&gt;. Again, we use the delegate pattern to ask the &lt;code&gt;EditViewController&lt;/code&gt; about the &lt;code&gt;referenceSize&lt;/code&gt; that’s needed to calculate the sticker’s normalized size.&lt;/p&gt;
&lt;h2 id=&quot;brushviewcontroller&quot;&gt;BrushViewController&lt;/h2&gt;
&lt;p&gt;This view controller, just as described above, is just a wrapper around the &lt;code&gt;BrushEditController&lt;/code&gt; that adds a color and size selection. Once again, all interaction is handled through delegates and upon closing of the brush tool, the internal model is updated. To support &lt;em&gt;undo&lt;/em&gt;, we need to pass the &lt;code&gt;PreviewEditViewController&lt;/code&gt;’s &lt;code&gt;undoController&lt;/code&gt; and begin a new group, whenever we fire up the tool.&lt;/p&gt;
&lt;h2 id=&quot;textviewcontroller&quot;&gt;TextViewController&lt;/h2&gt;
&lt;p&gt;The text editor Instagram created within their Stories UI is essentially a fullscreen textfield with additional controls for color and size selection. Ignoring the customized design, this can easily be recreated using stock iOS components and essentially only requires listening for keyboard notifications to handle the layout and adjusting the textfield’s properties according to user interactions. Once finished, we create a corresponding &lt;code&gt;TextSpriteModel&lt;/code&gt; that is positioned below the &lt;code&gt;UITextField&lt;/code&gt; and notify the &lt;code&gt;EditViewController&lt;/code&gt; using the delegate. After dismissing the &lt;code&gt;TextViewController&lt;/code&gt; the text rendering is handled by the &lt;code&gt;PhotoEditPreviewController&lt;/code&gt;. When reselecting an existing label, we just prefill the &lt;code&gt;UITextField&lt;/code&gt; with it’s contents and update the model when the label textfield changes. To ensure the &lt;code&gt;UITextField&lt;/code&gt; is the only place the text is currently visible, we hide the &lt;code&gt;spriteView&lt;/code&gt; we previously selected and show it again, once editing has finished.&lt;/p&gt;
&lt;h2 id=&quot;the-result&quot;&gt;The Result&lt;/h2&gt;
&lt;p&gt;Recreating the Instagram Stories UI seemed rather challenging at first, but using our &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt;, it quickly turned into a very rewarding project. Being able to create something like a brush tool without the need to actually implement any drawing or gesture handling is amazing and helped to create a working prototype in no time. Tricky details like sprite transformations, remote loading of stickers or undo management were entirely handled by the SDK and we could completely focus on the interface itself.&lt;/p&gt;
&lt;p&gt;For more details and some actual code, take a look at the &lt;a href=&quot;https://github.com/imgly/pesdk-blog-instagram-ui&quot;&gt;repository&lt;/a&gt;. We documented all tricky parts and you should be able to start your own implementation using the code. Please keep in mind, that the code may target a slightly older PhotoEditor SDK version, so check for a new version before you start your journey.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading! To stay in the loop, subscribe to our &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;Newsletter&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Malte</dc:creator><media:content url="https://blog.img.ly/downloaded_images/How-to-build-Instagram-s-Story-Editor-in-a-Day/1-iNnmjNai7mPywoKsgLz-xg.png" medium="image"/><category>iOS</category><category>Swift</category><category>Instagram</category><category>Snapchat</category><category>Mobile App Development</category><category>Tech</category><category>How-To</category><category>Tutorial</category><category>Learning</category></item></channel></rss>