<?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>Image Processing – IMG.LY Blog</title><description>Posts tagged Image Processing on the IMG.LY blog.</description><link>https://img.ly/blog/tag/image-processing/</link><language>en-us</language><image><url>https://img.ly/apple-touch-icon.png</url><title>Image Processing – IMG.LY Blog</title><link>https://img.ly/blog/tag/image-processing/</link></image><atom:link href="https://img.ly/blog/tag/image-processing/rss.xml" rel="self" type="application/rss+xml"/><generator>Astro</generator><lastBuildDate>Wed, 24 Jun 2026 09:29:44 GMT</lastBuildDate><ttl>60</ttl><item><title>Background Removal in the Browser Using ONNX Runtime with WebGPU</title><link>https://img.ly/blog/browser-background-removal-using-onnx-runtime-webgpu/</link><guid isPermaLink="true">https://img.ly/blog/browser-background-removal-using-onnx-runtime-webgpu/</guid><description>Achieve 20x Faster Background Removal in the Browser</description><pubDate>Tue, 11 Jun 2024 16:26:17 GMT</pubDate><content:encoded>&lt;p&gt;TL;DR: Using ONNX Runtime with WebGPU and WebAssembly leads to 20x speedup over multi-threaded and 550x speedup over single-threaded CPU performance. Thus achieving interactive speeds for state-of-the-art background removal directly in the browser.&lt;/p&gt;
&lt;p&gt;Removing background from an image is a typical job to be done in creative editing. We have come a long way from manually knocking out the background from an image to full automation with Neural Networks.&lt;/p&gt;
&lt;p&gt;Most state-of-the-art background removal solutions work by offloading the task to the server with a GPU as it was simply infeasible to run the NN on the client.&lt;/p&gt;
&lt;p&gt;However, running background removal directly in the browser offers several advantages over server-side processing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reduced server load and infrastructure costs by offloading heavy lifting to the client.&lt;/li&gt;
&lt;li&gt;Enhanced scalability by distributing the workload across client devices.&lt;/li&gt;
&lt;li&gt;Easier compliance with data protection and security policies by not transferring data across a network to a server.&lt;/li&gt;
&lt;li&gt;Offline processing without needing a reliable internet connection.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It caters to a wide range of use cases, including but not limited to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;E-commerce applications&lt;/em&gt; that need to remove backgrounds from product images in real time.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Image editing applications&lt;/em&gt; that require background removal capabilities for enhancing user experience.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Web-based graphic design tools&lt;/em&gt; that aim to simplify the creative process with in-browser background removal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In general, two factors influence the feasibility of running background removal directly on the client.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The execution performance, and&lt;/li&gt;
&lt;li&gt;the download size of the Neural Network.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The performance or overall runtime is the major factor to be useful in interactive applications, if a user has to wait several minutes or hours for a neural network to execute, this is in many cases far too long in terms of good user experience. From experience, there are three factors to consider.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The initial first-time execution. The major factor is that neural networks come with the drawback of generally being several MB to GB in size, thus the time to download the neural network into the browser cache is considerable. In subsequent browser page reloads this has no impact anymore.&lt;/li&gt;
&lt;li&gt;The neural network or session initialization time, cannot be cached and has to run with every reload of the page in the browser.&lt;/li&gt;
&lt;li&gt;The neural network or session inference time, largely depends on the longest path inside the neural network and most importantly the execution time of each operator in the neural network.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;towards-real-time-background-removal-in-the-browser&quot;&gt;Towards Real-time Background Removal in the Browser&lt;/h3&gt;
&lt;p&gt;Neural networks are commonly trained in frameworks like PyTorch, which is a neural network library for Python, as such not usable directly in the browser. The best option to run neural networks directly in the browser is converting the neural network into the &lt;strong&gt;Open Neural Network Exchange (ONNX)&lt;/strong&gt; format, which is a widely supported standardized format by Microsoft used extensively in the industry.&lt;/p&gt;
&lt;p&gt;These ONNX-formatted neural networks can then be reconverted into a platform-specific format or directly executed by a supported runtime.&lt;/p&gt;
&lt;p&gt;The ONNX Runtime by Microsoft is a high-performance inference engine designed to run ONNX models across various platforms and languages. One notable feature is &lt;a href=&quot;https://onnxruntime.ai/docs/tutorials/web/build-web-app.html&quot;&gt;ONNX Runtime Web&lt;/a&gt;, which allows JavaScript developers to execute ONNX models directly in the browser. ONNX Runtime Web offers several execution providers for hardware acceleration. For instance, its WebAssembly execution provider enhances CPU execution performance using multiple Web Workers and SIMD instructions.&lt;/p&gt;
&lt;p&gt;More importantly, starting from version 1.7.0, ONNX Runtime Web includes official support for the WebGPU execution provider. WebGPU is a modern web API that enables developers to utilize GPU power for high-performance computations, offering a significant performance boost over CPU-based in-browser machine learning. WebGPU support has been available by default since Chrome 113 and Edge 113 on Mac, Windows, and ChromeOS, and Chrome 121 on Android. For the latest browser support updates, you can track them &lt;a href=&quot;https://github.com/gpuweb/gpuweb/wiki/Implementation-Status&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;implementation-details&quot;&gt;Implementation Details&lt;/h3&gt;
&lt;p&gt;For the implementation of the open-source package &lt;a href=&quot;https://github.com/imgly/background-removal-js&quot;&gt;@imgly/background-removal-js&lt;/a&gt;, we started the journey with a neural network implementation in PyTorch written in Python. The network was then converted to ONNX. You can see our &lt;a href=&quot;https://img.ly/demos/background-removal/web/&quot;&gt;live showcase&lt;/a&gt; here.&lt;/p&gt;
&lt;p&gt;The original model was using 32-bit floating point (fp32) precision, which is fine, but results in a file size of 168 MB after converting it to ONNX. As mentioned earlier, the size of the network has a large impact on perceived first-time execution performance as the download time tends to be longer than the execution time, but more to that later.&lt;/p&gt;
&lt;p&gt;To reduce the size of the model, we converted the model to use fp16 (16-bit floating point) and QUINT8 (Quantized 8-bit) datatypes. Thus, effectively reducing the size to half (84MB) and a fourth (42MB) of the original size. Additionally to the download size, the operators used will be converted corresponding to the datatype and different data types and depending on the hardware may lead to speed improvements or even deteriorating due to specialized hardware being used or not being present.&lt;/p&gt;
&lt;p&gt;Note, that while that sounds great, the conversion has a potential negative impact on the quality of the output as we are removing information in the neural networks and since we are working with images, artifacts might become visible and the quality of the resulting background mask is reduced.&lt;/p&gt;
&lt;h2 id=&quot;evaluation&quot;&gt;Evaluation&lt;/h2&gt;
&lt;p&gt;When dealing with neural networks in the browser we can identify three different scenarios&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First Use&lt;/li&gt;
&lt;li&gt;First Run&lt;/li&gt;
&lt;li&gt;Consecutive Runs&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first-use scenario occurs when the web application is started the first time. The neural network has to be downloaded from the server into the browser sandbox, as neural networks are several 10-1000 MB in size this is not neglectable.&lt;/p&gt;
&lt;p&gt;The first run assumes that the neural network is already present in the browser cache, however, when the neural network ought to be used, the network has to initialize before execution.&lt;/p&gt;
&lt;p&gt;In consecutive runs, the neural network is already in memory and the execution time is largely determined by the neural network depth and operators only.&lt;/p&gt;
&lt;h3 id=&quot;first-use--neural-network-download&quot;&gt;First Use – Neural Network Download&lt;/h3&gt;
&lt;p&gt;While download or time is a large factor in the first-time execution time, this is largely dependent on the available network bandwidth, as such it is not part of the evaluation but in order to get an idea here are the expected download times for the fp32 (168MB) and fp16 (84MB) neural network subject to various common network bandwidth:&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Network Bandwidth&lt;/th&gt;&lt;th&gt;Filesize&lt;/th&gt;&lt;th&gt;Download Time&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;10 Mbps&lt;/td&gt;&lt;td&gt;84 MB&lt;/td&gt;&lt;td&gt;67s s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;100 Mbps&lt;/td&gt;&lt;td&gt;84 MB&lt;/td&gt;&lt;td&gt;6.7 s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1 Gbps&lt;/td&gt;&lt;td&gt;84 MB&lt;/td&gt;&lt;td&gt;0.67 s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;10 Gbps&lt;/td&gt;&lt;td&gt;84 MB&lt;/td&gt;&lt;td&gt;0.067&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Network Bandwidth&lt;/th&gt;&lt;th&gt;Filesize&lt;/th&gt;&lt;th&gt;Download Time&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;10 Mbps&lt;/td&gt;&lt;td&gt;168 MB&lt;/td&gt;&lt;td&gt;134.4 s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;100 Mbps&lt;/td&gt;&lt;td&gt;168 MB&lt;/td&gt;&lt;td&gt;13.4 s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1 Gbps&lt;/td&gt;&lt;td&gt;168 MB&lt;/td&gt;&lt;td&gt;1.34 s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;10 Gbps&lt;/td&gt;&lt;td&gt;168 MB&lt;/td&gt;&lt;td&gt;0.13 s&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Based on the assumption that the median download bandwidth is ~100 Mbps, we see that it’s in the 5-15 second range.&lt;/p&gt;
&lt;h3 id=&quot;first-run--neural-network-initialization--compilation&quot;&gt;First Run – Neural Network Initialization / Compilation&lt;/h3&gt;
&lt;p&gt;Before the neural network can be executed it has to be initialized. Initialization includes several execution provider-specific steps. Most prominent are the time to upload or convert the data to the execution provider and execution provider-specific ONNX graph optimization passes. This all adds to the first run experience. We have evaluated the average session initialization time for the CPU (WASM) and WebGPU provider on a MacBook Pro 13” from 2024 with an Apple M3 Max 16 cores to get an idea of the general impact on first run execution time:&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Device&lt;/th&gt;&lt;th&gt;Datatype&lt;/th&gt;&lt;th&gt;Session Initialization Time&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;CPU (WASM)&lt;/td&gt;&lt;td&gt;fp32&lt;/td&gt;&lt;td&gt;~320ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CPU (WASM)&lt;/td&gt;&lt;td&gt;fp16&lt;/td&gt;&lt;td&gt;~320ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;WebGPU&lt;/td&gt;&lt;td&gt;fp32&lt;/td&gt;&lt;td&gt;~400ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;WebGPU&lt;/td&gt;&lt;td&gt;fp16&lt;/td&gt;&lt;td&gt;~200ms&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Note, that session initialization is not negligible and adds significant runtime overhead and might be subject to additional optimization possibilities like caching the optimized model.&lt;/p&gt;
&lt;h3 id=&quot;consecutive-runs--neural-network-execution&quot;&gt;Consecutive Runs – Neural Network Execution&lt;/h3&gt;
&lt;p&gt;Independent of the download and initialization time, we evaluated different execution providers on a MacBook Pro 13” from 2024 with an Apple M3 Max 16 cores. While this is top-end consumer hardware, the general trends will probably apply to various hardware configurations. As a reference, we choose the single thread performance with the neural network running on the CPU with 1 worker thread and SIMD disabled.&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Device&lt;/th&gt;&lt;th&gt;SIMD&lt;/th&gt;&lt;th&gt;Threads&lt;/th&gt;&lt;th&gt;Datatype&lt;/th&gt;&lt;th&gt;Session Runtime&lt;/th&gt;&lt;th&gt;Speedup&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;CPU (WASM)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;fp32&lt;/td&gt;&lt;td&gt;~53000 ms&lt;/td&gt;&lt;td&gt;1.0 x&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CPU (WASM)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;td&gt;16&lt;/td&gt;&lt;td&gt;fp32&lt;/td&gt;&lt;td&gt;~6300 ms&lt;/td&gt;&lt;td&gt;8.4 x&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CPU (WASM)&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;fp32&lt;/td&gt;&lt;td&gt;~15000 ms&lt;/td&gt;&lt;td&gt;3.5 x&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CPU (WASM)&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;16&lt;/td&gt;&lt;td&gt;fp32&lt;/td&gt;&lt;td&gt;~2000 ms&lt;/td&gt;&lt;td&gt;26.5 x&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The data above reveals that running the neural network without any acceleration such as SIMD and threading in the browser results in almost 53s runtime, and as such is for most interactive use cases too slow to use. Due to the optimizations of the ONNX Runtime that uses SIMD and threads, we can achieve an overall speedup of roughly ~26 times compared to the baseline performance. Thus decreasing the session runtime to around 2s and making it usable for interactive applications.&lt;/p&gt;
&lt;p&gt;As mentioned before, we neglected the download time and size of the network. Leveraging the fp16 model compression, we re-ran the benchmarks and got similar results as before, but with half the bandwidth required to download the network for the first time application. The general visual performance of the fp16 and fp32 is similar or visually not perceivable. The quint8 model led to artifacts and as such unusable for visual processing, thus we excluded from the following benchmark.&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Device&lt;/th&gt;&lt;th&gt;SIMD&lt;/th&gt;&lt;th&gt;Threads&lt;/th&gt;&lt;th&gt;Datatype&lt;/th&gt;&lt;th&gt;Session Runtime&lt;/th&gt;&lt;th&gt;Speedup&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;CPU (WASM)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;fp16&lt;/td&gt;&lt;td&gt;~55000 ms&lt;/td&gt;&lt;td&gt;1.0 x&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CPU (WASM)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;td&gt;16&lt;/td&gt;&lt;td&gt;fp16&lt;/td&gt;&lt;td&gt;~7300 ms&lt;/td&gt;&lt;td&gt;7.2 x&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CPU (WASM)&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;fp16&lt;/td&gt;&lt;td&gt;~15000 ms&lt;/td&gt;&lt;td&gt;3.5 x&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CPU (WASM)&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;16&lt;/td&gt;&lt;td&gt;fp16&lt;/td&gt;&lt;td&gt;~2300 ms&lt;/td&gt;&lt;td&gt;23.9 x&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The results are a little worse than the fp32 version, which might be because the fp16 datatype has no direct specialized hardware in modern CPUs like the M3 Max CPU, and as such additional fp16 to fp32 conversion operations have to be performed.&lt;/p&gt;
&lt;p&gt;Our final benchmark measures the WebGPU performance, which led to the following impressive results. To have a fair comparison and understanding of the impact of the WebGPU technology, we compare it with the best CPU version with 16 threads and SIMD enabled with the fp32 model, which was around ~2 s.&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Device&lt;/th&gt;&lt;th&gt;SIMD&lt;/th&gt;&lt;th&gt;Threads&lt;/th&gt;&lt;th&gt;Datatype&lt;/th&gt;&lt;th&gt;Session Runtime&lt;/th&gt;&lt;th&gt;Speedup&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;WebGPU&lt;/td&gt;&lt;td&gt;Not applicable&lt;/td&gt;&lt;td&gt;Not applicable&lt;/td&gt;&lt;td&gt;fp32&lt;/td&gt;&lt;td&gt;~120ms&lt;/td&gt;&lt;td&gt;16.6 x&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;WebGPU&lt;/td&gt;&lt;td&gt;Not applicable&lt;/td&gt;&lt;td&gt;Not applicable&lt;/td&gt;&lt;td&gt;fp16&lt;/td&gt;&lt;td&gt;~100ms&lt;/td&gt;&lt;td&gt;20.0 x&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The WebGPU performance varies around &lt;strong&gt;16 to 20 x&lt;/strong&gt; improvements over the best CPU execution time. The GPU has specialized hardware for fp16 instructions, as such the model performs better than the fp32 model. Also, note that the session initialization time is also reduced due to half the required bandwidth to upload the network data to the GPU.&lt;/p&gt;
&lt;p&gt;Therefore, the first run of the network will take ~300 ms and consecutive runs will be ~100 ms, leading to near real-time performance in the browser.&lt;/p&gt;
&lt;p&gt;Note, that the WebGPU performance is an astonishing &lt;strong&gt;550 times faster&lt;/strong&gt; than the single thread, with no SIMD performance.&lt;/p&gt;
&lt;p&gt;Try the live implementation in our &lt;a href=&quot;https://img.ly/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=background-removal-onnx&quot;&gt;background removal showcase&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;WebGPU is a major leap in establishing the browser as a factor to be reckoned with as a true Application platform. With &lt;a href=&quot;https://IMG.LY/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=background-removal-onnx&quot;&gt;IMG.LY&lt;/a&gt;, we are striving to leverage modern technology to make design tools accessible, this includes on-device execution, on-premise, but also on-cloud execution of design tools leveraging neural networks.&lt;/p&gt;
&lt;p&gt;As a next step, we will port background removal to all our supported platforms, ONNX Runtime seems the best choice, as it is already available for all the potential platforms we support.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/onnx/bg-removal.mp4&quot; controls autoplay muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Furthermore, we are evaluating the feasibility of in-browser background removal for videos to be included with our video-editing suits.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://img.ly/demos/&quot;&gt;Try our tools today&lt;/a&gt; to see how we help bring unique creative editing experiences to any application. Or &lt;a href=&quot;https://img.ly/forms/contact-sales/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=background-removal-onnx&quot;&gt;contact us&lt;/a&gt; to discuss your project.&lt;/p&gt;</content:encoded><dc:creator>Daniel</dc:creator><dc:creator>Emma</dc:creator><media:content url="https://blog.img.ly/2024/06/onnx-runtime-imgly.jpg" medium="image"/><category>Plugin</category><category>App Development</category><category>Image Processing</category><category>Insights</category></item><item><title>How To Add a Sticker to a Texture With Three.js</title><link>https://img.ly/blog/how-to-add-a-sticker-to-a-texture-with-three-js/</link><guid isPermaLink="true">https://img.ly/blog/how-to-add-a-sticker-to-a-texture-with-three-js/</guid><description>Learn how to add an overlay image to your textures in WebGL and JavaScript with Three.js.</description><pubDate>Fri, 25 Feb 2022 15:10:58 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Disclaimer: This article has been updated in June 2023 to reflect the changes introduced by &lt;a href=&quot;https://github.com/mrdoob/three.js/wiki/Migration-Guide#149--150&quot;&gt;Three.js r150&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this article, you will learn to add a sticker to a texture with &lt;a href=&quot;https://get.webgl.org/&quot;&gt;WebGL&lt;/a&gt;. In detail, you will see how to use the &lt;a href=&quot;https://threejs.org/&quot;&gt;Three.js&lt;/a&gt; JavaScript library to add an overlay image to a texture. Check out our guide to explore other image editing operations, such as &lt;a href=&quot;https://img.ly/blog/how-to-resize-an-image-in-react/&quot;&gt;resizing an image in React&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Dealing with 3D graphics used to be a challenging, complex, and time-consuming task. Thanks to libraries like &lt;code&gt;three.js&lt;/code&gt;, it has become more accessible than ever. Follow this easy step-by-step how-to and achieve the following result:&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Here are all the prerequisites you need to meet to follow this tutorial and build the demo application shown above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nodejs.org/en&quot;&gt;Node.js &gt;= 18&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mrdoob/three.js&quot;&gt;Three.js &gt;= r150&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Explore the official documentation page to learn about &lt;a href=&quot;https://threejs.org/docs/#manual/en/introduction/Installation&quot;&gt;how to install &lt;code&gt;three.js&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-is-threejs&quot;&gt;What is Three.js?&lt;/h2&gt;
&lt;p&gt;As stated in the &lt;a href=&quot;https://github.com/mrdoob/three.js/&quot;&gt;GitHub page of the project&lt;/a&gt;, the goal of the Three.js project is to create a lightweight 3D JavaScript library with a very low level of complexity. In other words, Three.js aims to make 3D graphics more accessible, especially considering that Three.js only requires a browser to run.&lt;/p&gt;
&lt;p&gt;To be more specific, it allows you to create a 3D graphics project where you can modify elements in your browser and see visual feedback. The library is currently built on top of a &lt;a href=&quot;https://threejs.org/docs/#api/en/renderers/WebGLRenderer&quot;&gt;WebGL renderer&lt;/a&gt;, but it also supports &lt;a href=&quot;https://en.wikipedia.org/wiki/WebGPU&quot;&gt;WebGPU&lt;/a&gt;, &lt;a href=&quot;https://threejs.org/docs/#examples/en/renderers/SVGRenderer&quot;&gt;SVG&lt;/a&gt;, and &lt;a href=&quot;https://threejs.org/docs/#examples/en/renderers/CSS3DRenderer&quot;&gt;CSS3D&lt;/a&gt; renderers.&lt;/p&gt;
&lt;p&gt;If you are not familiar with these concepts, &lt;a href=&quot;https://en.wikipedia.org/wiki/WebGL&quot;&gt;WebGL&lt;/a&gt; stands for “Web Graphics Library” and is a JavaScript API for rendering high-performance interactive 3D and 2D graphics. You can use it in many web browsers without external plugins or libraries.&lt;/p&gt;
&lt;p&gt;As you can verify from the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API#browser_compatibility&quot;&gt;MDN compatibility page&lt;/a&gt;, browsers currently support the most popular WebGL features. However, keep in mind that the user’s device must meet hardware requirements for some features to work.&lt;/p&gt;
&lt;p&gt;WebGL is undoubtedly a powerful tool but requires a lot of skill and involves a lot of boilerplate code. Here is where Three.js comes into play, making 3D development much easier.&lt;/p&gt;
&lt;p&gt;Now, let’s see it in action!&lt;/p&gt;
&lt;h2 id=&quot;adding-an-overlay-image-to-a-texture-in-webgl&quot;&gt;Adding an Overlay Image to a Texture in WebGL&lt;/h2&gt;
&lt;p&gt;Clone the &lt;a href=&quot;https://github.com/Tonel/how-to-add-a-sticker-with-threejs-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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; clone&lt;/span&gt;&lt;span&gt; https://github.com/Tonel/how-to-add-a-sticker-with-threejs-imgly&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Additionally, try the demo application by launchingthe command below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, visit &lt;code&gt;http://localhost:5000&lt;/code&gt; in the browser.&lt;/p&gt;
&lt;p&gt;Now that you know what the demo app looks like, keep following this tutorial and learn how to build it.&lt;/p&gt;
&lt;p&gt;Specifically, this is the entire JavaScript code required to achieve the goal:&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; *&lt;/span&gt;&lt;span&gt; as&lt;/span&gt;&lt;span&gt; THREE &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;three&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; { OrbitControls } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;three/addons/controls/OrbitControls.js&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;// set up the scene, camera, and renderer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; scene&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Scene&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; camera&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PerspectiveCamera&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&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;  window.innerWidth &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; window.innerHeight,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&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;  1000&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;camera.position.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;150&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;150&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; OrbitControls&lt;/span&gt;&lt;span&gt;(camera, document.body);&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;// initialize the WebGL renderer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; renderer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WebGLRenderer&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;renderer.&lt;/span&gt;&lt;span&gt;setSize&lt;/span&gt;&lt;span&gt;(window.innerWidth, window.innerHeight);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;document.body.&lt;/span&gt;&lt;span&gt;appendChild&lt;/span&gt;&lt;span&gt;(renderer.domElement);&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;// add light to the scene to make the texture visible&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; light&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AmbientLight&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0xffffff&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scene.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(light);&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;// add the axes helper indicator to the scene&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scene.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;AxesHelper&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;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// load the base texture&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; texture&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;TextureLoader&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;https://threejs.org/examples/textures/hardwood2_diffuse.jpg&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 class=&quot;line&quot;&gt;&lt;span&gt;// create a new buffer geometry and place it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// to the horizontal plane&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; textureGeometry&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PlaneGeometry&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;50&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;textureGeometry.&lt;/span&gt;&lt;span&gt;rotateX&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;90&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.MathUtils.&lt;/span&gt;&lt;span&gt;DEG2RAD&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// translate the plane to the center of the scene&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;textureGeometry.&lt;/span&gt;&lt;span&gt;translate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;25&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;25&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;// create a MeshBasicMaterial with the base texture&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; textureMaterial&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MeshBasicMaterial&lt;/span&gt;&lt;span&gt;({ map: texture });&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 a mesh using the geometry and material&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// and add it to the scene&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; textureMesh&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Mesh&lt;/span&gt;&lt;span&gt;(textureGeometry, textureMaterial);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scene.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(textureMesh);&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;// repeat the same logic to place the sticker image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// over the texture&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; sticker&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;TextureLoader&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;https://i.imgur.com/IYh17Rv.png&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 class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; stickerGeometry&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PlaneGeometry&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;20&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;stickerGeometry.&lt;/span&gt;&lt;span&gt;rotateX&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;90&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.MathUtils.&lt;/span&gt;&lt;span&gt;DEG2RAD&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;stickerGeometry.&lt;/span&gt;&lt;span&gt;translate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;25&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;25&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; stickerMaterial&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MeshBasicMaterial&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  map: sticker,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  transparent: &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;span class=&quot;line&quot;&gt;&lt;span&gt;// create a mesh using the image geometry and material&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; imageMesh&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Mesh&lt;/span&gt;&lt;span&gt;(stickerGeometry, stickerMaterial);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scene.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(imageMesh);&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;// adjust the position and rotation of the sticker mesh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// by setting the y-coordinate to 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// in order for the sticker to rest on the texture&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;imageMesh.position.&lt;/span&gt;&lt;span&gt;set&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;1&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;// render the scene&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; launchThreeJs&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  requestAnimationFrame&lt;/span&gt;&lt;span&gt;(launchThreeJs);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  renderer.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(scene, camera);&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;launchThreeJs&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, the code imports the necessary components from the Three.js library. Next, it initialize a &lt;a href=&quot;https://threejs.org/docs/#api/en/scenes/Scene&quot;&gt;&lt;code&gt;Scene&lt;/code&gt;&lt;/a&gt; and set up a &lt;a href=&quot;https://threejs.org/docs/#api/en/cameras/PerspectiveCamera&quot;&gt;&lt;code&gt;PerspectiveCamera&lt;/code&gt;&lt;/a&gt; with &lt;a href=&quot;https://threejs.org/docs/#examples/en/controls/OrbitControls&quot;&gt;&lt;code&gt;OrbitsControl&lt;/code&gt;&lt;/a&gt;, allowing the user to move around. Then, it creates a WebGL renderer and sets its size to match the window’s dimensions. The scene is lit by an &lt;a href=&quot;https://threejs.org/docs/#api/en/lights/AmbientLight&quot;&gt;&lt;code&gt;AmbientLight&lt;/code&gt;&lt;/a&gt; instance, which illuminates all objects in the scene equally.&lt;/p&gt;
&lt;p&gt;The snippet loads a base texture using TextureLoader from the given URL, creates a &lt;a href=&quot;https://threejs.org/docs/#api/en/geometries/PlaneGeometry&quot;&gt;&lt;code&gt;PlaneGeometry&lt;/code&gt;&lt;/a&gt;, defines a mesh with some geometries, and then add the resulting element to the scene. This logic will take care of rendering the texture image in the scene.&lt;/p&gt;
&lt;p&gt;To add the sticker to the texture, the same procude gets repeated. This time, Three.js is instructed to load a sticker image and position it over the original texture. Note that the position of the sticker mesh is adjusted by setting its y-coordinate to 1 to ensure the new image rests on top of the old one.&lt;/p&gt;
&lt;p&gt;Et voilà! You just learned how to add a sticker to a WebGL texture with &lt;code&gt;three.js&lt;/code&gt;!&lt;/p&gt;
&lt;h2 id=&quot;adding-a-sticker-with-photoeditorsdk&quot;&gt;Adding a Sticker With &lt;a href=&quot;https://www.npmjs.com/package/photoeditorsdk&quot;&gt;&lt;code&gt;PHOTOEDITORSDK&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://img.ly/docs/pesdk/web/introduction/migration-guide/#canvas-renderer/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;PhotoEditorSDK uses WebGL as the main renderer&lt;/a&gt;. You can effortlessly add a sticker to your textures without writing any line of code. WebGL will be used behind the scene. Consequently, you do not need to know WebGL or code in GLSL to harness its power.&lt;/p&gt;
&lt;p&gt;Adding a sticker to an image only takes a few seconds here. Just jump to &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 article&lt;/a&gt; from the official documentation and learn how to get started with PhotoEditorSDK in Vanilla JavaScript. Then, upload your texture image and a sticker with the &lt;a href=&quot;https://img.ly/docs/pesdk/web/features/stickers/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;Sticker feature&lt;/a&gt;. As shown below, you can position a sticker on top of the original image with a point-and-click UI:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;how-add-sticker-to-texture-image-three-js-webgl&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 700px) 700px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;700&quot; height=&quot;376&quot; src=&quot;https://img.ly/_astro/how-add-sticker-to-texture-image-three-js-webgl_1IEwVR.webp&quot; srcset=&quot;/_astro/how-add-sticker-to-texture-image-three-js-webgl_29mIIu.webp 640w, /_astro/how-add-sticker-to-texture-image-three-js-webgl_1IEwVR.webp 700w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Check out this feature on the &lt;a href=&quot;https://img.ly/products/photo-sdk/&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;Today we learned how to add an overlay image to a WebGL texture in JavaScript with &lt;code&gt;three.js&lt;/code&gt;. This tool makes it easier to deal with WebGL and 3D graphics. In particular, by using one of the many plugins supporting the Three.js project, we were able to add a sticker to a texture with just a bunch of lines of code.&lt;/p&gt;
&lt;p&gt;3D graphics remains a complex topic, especially when it comes to developing in the OpenGL Shading Language. Three.js tries to hide this encumbrance as much as possible, but sometimes you still require it. If you want to avoid using WebGL directly to achieve your goal, consider an all-in-one and easy-to-use 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.&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>Antonello</dc:creator><media:content url="https://blog.img.ly/2022/02/add-sticker-to-image-texture-three-js-threejs.png" medium="image"/><category>How-To</category><category>Image Editing</category><category>Image Processing</category><category>3D</category><category>Photo Editing</category><category>Stickers</category><category>Tech</category><category>Tutorial</category></item><item><title>On the Democratization of Design</title><link>https://img.ly/blog/on-the-democratization-of-design-ce731e9374f0/</link><guid isPermaLink="true">https://img.ly/blog/on-the-democratization-of-design-ce731e9374f0/</guid><description>Creating appealing designs remains hard for most of us, and there are many reasons for that. </description><pubDate>Mon, 24 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Over the last century we have experienced the democratization of various domains of our lives:&lt;/p&gt;
&lt;p&gt;Key drivers have been technological advancements and lower barriers to leveraging those technologies. In turn, technology became more accessible to more people resulting in products, services, and tools that improve our lives every day.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Computers are a bicycle for the mind” — Steve Jobs&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Tools, such as computers, allow us to finish more tasks in a shorter amount of time and with fewer resources. Even more importantly, said tools also rid us from boring and repetitive work, thus enabling us to shift our attention to exciting and more creative tasks, paving the way for us to experiment and create rather than repeat.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;evolve-design-with-bettertools&quot;&gt;Evolve Design with better Tools&lt;/h2&gt;
&lt;p&gt;Proper visual design attracts, engages, and persuades; it helps convey stories and facilitates communication.&lt;/p&gt;
&lt;p&gt;Nowadays, design in all its facets like advertising, social stories, product presentations, or merchandise is omnipresent in our daily lives. As there is no arguing that appealing and beautiful design has an impact on us and our surrounding, consequently there is also a growing demand for good design and easy-to-use tools.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Use a picture. It’s worth a thousand words!” — Arthur Brisbamne&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Although the importance of design is unquestioned, creating appealing designs remains hard for most of us, and there are many reasons for that.&lt;/p&gt;
&lt;p&gt;First of all, design tools are still challenging to master, and while they empower professional designers to create stunning assets, they remain a closed book for most people.&lt;/p&gt;
&lt;p&gt;Secondly, even if we leave most of the creational part to professionals, the average user is still unable to use existing designs as a basis to create new assets or refine and update small details. Even for the slightest changes, one has to get familiar with the inner workings of sophisticated design tools.&lt;/p&gt;
&lt;p&gt;Also, when it comes to collaboration, the process with most tools is far from frictionless, to put it mildly. Still, most designs get shared as Photoshop, Sketch, or PowerPoint files, and there is no tool or platform that grants unhindered access for multiple stakeholders with varying degrees of experience with design solutions.&lt;/p&gt;
&lt;p&gt;As today’s design processes regularly involve multiple stakeholders like designers, marketers, copywriters, or product engineers, one has to acknowledge that prevalent design solutions simply lack features and workflows that reflect daily routines.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;make-design-accessible-through-technology&quot;&gt;Make Design Accessible through Technology&lt;/h2&gt;
&lt;p&gt;As we pointed out, the overall access to good visual design is still very limited, but with the growing need for compelling creatives in virtually every aspect of the digital and non-digital realm this shouldn’t be and it doesn’t have to. We believe in the democratization of design.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Better tools enable the democratization of design!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We believe that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We need to overhaul design processes and methods to cope with the ever growing design needs and the ever-rising number of stakeholders.&lt;/li&gt;
&lt;li&gt;Achieving elegant design must be made simple.&lt;/li&gt;
&lt;li&gt;Design can be made accessible through technology.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The question remains: How do we achieve that?&lt;/p&gt;
&lt;p&gt;We came to the conclusion that there are four key elements design tools should have that will make design more accessible:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Collaboration,&lt;/li&gt;
&lt;li&gt;building blocks of intelligent design components,&lt;/li&gt;
&lt;li&gt;automation of tedious design tasks, and&lt;/li&gt;
&lt;li&gt;bringing together Editing, Compositing, and Layouting.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Collaboration&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We hold as an opinion that design tools have to facilitate the collaboration between professionals and non-professionals, thus allowing an effective and frictionless design creation and adaption process. This includes direct collaboration on a design as well as the exchange of whole design templates, design components, and presets.&lt;/p&gt;
&lt;p&gt;While some solutions already offer templates for content creation, we think that design components as a more modular approach are going to be a necessity to simplify the creation of new complex assets for everyone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Building Blocks of Intelligent Design Components&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If Lego™ taught us anything, then it is that creativity can flow when you base your work on a solid foundation.&lt;/p&gt;
&lt;p&gt;That said, one has to admit that while the basic elements of design such as text, shapes, or images can be creatively combined in millions of ways to create stunning designs, they also can be used in a trillion ways to create underwhelming output.&lt;/p&gt;
&lt;p&gt;Similar to modern UI design, complex building blocks will play a central role in the visual design process, allowing creators to be more efficient by removing repetitive tasks from the workflow and also allowing average users to create and adopt complex designs quickly. Intelligent design components will shift the focus to the composition by guiding the user in carefully chosen constraints.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Automation of Tedious Design Tasks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Speaking of intelligent building blocks, we believe that said blocks must not be static but adaptive to fit into an effective and frictionless design process. That may include the automation of certain design tasks like text layout or even abstracting away the complexity of image editing.&lt;/p&gt;
&lt;p&gt;We envision that with the help of technology and specifically artificial intelligence, recurring and taunting tasks like for example, the separation of a picture into fore- and background can become totally interaction- and hassle-free. Even automatically choosing the right adjustments based on the image content will be possible.&lt;/p&gt;
&lt;p&gt;Altogether, intelligent design components are a necessity and will be the next step in design automation, fostering a flexible and fail-safe adaption and creation process and thus ensuring that even without professional knowledge designs will be created faster and look better.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bringing together Editing, Composition, and Layouting&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Photo editing and layouting are both central to the design process. That is why, in a holistic approach, combining both will yield exciting new possibilities, improved visual consistency, and a reduced amount of friction while going back and forth between layout and photo editing.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;closing-words&quot;&gt;Closing words&lt;/h2&gt;
&lt;p&gt;To sum it up: To democratize design, we need intelligent design tools and ecosystems that allow for the cooperation between all stakeholders, as well as a community that is open to share and learn from one another by building and sharing reusable and intelligent design components.&lt;/p&gt;</content:encoded><dc:creator>Daniel</dc:creator><media:content url="https://blog.img.ly/downloaded_images/On-the-Democratization-of-Design/1-UUXCltgfNnl3pabjCsjjGg.jpeg" medium="image"/><category>Creativity</category><category>Design</category><category>Image Processing</category><category>Photoshop</category><category>Photo Editing</category><category>Insights</category></item><item><title>Lightweight Image comparison with Rembrandt.JS</title><link>https://img.ly/blog/lightweight-image-comparison-with-rembrandt-js-b189276d03c4/</link><guid isPermaLink="true">https://img.ly/blog/lightweight-image-comparison-with-rembrandt-js-b189276d03c4/</guid><pubDate>Wed, 08 Mar 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://github.com/imgly/rembrandt&quot;&gt;Rembrandt.JS&lt;/a&gt; is a lightweight, client- and server-side image comparison library that we developed for our internal tests for the &lt;a href=&quot;https://img.ly/products/photo-sdk/&quot;&gt;PhotoEditor SDK&lt;/a&gt;. Since we’re adherents of test-driven development, we devote a lot of time and effort to writing robust tests and finding the right tools. A lot can go wrong in the process of refactoring and adding new features to our product. Since we want to make sure that our SDK performs at a top level 100% of the time, solid unit tests are crucial to our work. However, when it came to image comparison, we were not happy with the existing options. We tried to work with Blink-Diff from Yahoo, but since the tool isn’t maintained anymore and furthermore has a dependency on another unmaintained library, we chose to develop our own solution. Rembrandt.JS has no dependencies because it works client-side with the Canvas API of the browser and server-side with Node-Canvas.&lt;/p&gt;
&lt;p&gt;The primary function of Rembrandt is quite simple; &lt;strong&gt;it compares two pictures&lt;/strong&gt;. One image is the expected output of our SDK and the other one is the actual output of the PhotoEditor. After the comparison, Rembrandt outputs a numeric value between 0 and 1 indicating the percentual error rate. With an additional feature of Rembrandt, it is also possible to highlight the incorrect pixels on the image which helps to see instantly where the errors occur. Furthermore, Rembrandt.JS can compensate defective pixels that originate through compression artifacts. By comparing every pixel with its adjacent pixels with an adjustable tolerance, Rembrandt evaluates whether there’s a discrepancy or not. Said feature was critical to us since due to the sheer amount of features and filters in PhotoEditor SDK we’re always testing on a large scale. If we would’ve been using loss-free test material (like .png for example) the amount of disk space that the images would’ve been consuming would have been quite noticeable.&lt;/p&gt;
&lt;p&gt;Rembrandt.JS could also be leveraged for UI tests. With screenshots of both the design and the actual website or app, you could verify whether every object loads and behaves correctly. With the rendering option, you could quickly detect smallest discrepancies in even large sites or apps. Or, you could solve every spot the difference game within seconds.&lt;/p&gt;
&lt;p&gt;If Rembrandt is something potentially useful for your project, check out the Github repositories for &lt;a href=&quot;https://github.com/imgly/rembrandt&quot;&gt;Javascript&lt;/a&gt;, iOS and Android.&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>Felix</dc:creator><media:content url="https://blog.img.ly/2020/04/1-nN176Cbkfr8ZA48HOY7JFA.png" medium="image"/><category>JavaScript</category><category>Software Development</category><category>Image Processing</category><category>iOS</category><category>Android</category><category>Tech</category><category>How-To</category><category>Insights</category></item></channel></rss>