<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title></title>
  <subtitle></subtitle>
  <id>https://www.endpointdev.com/blog/tags/graphics/</id>
  <link href="https://www.endpointdev.com/blog/tags/graphics/"/>
  <link href="https://www.endpointdev.com/blog/tags/graphics/" rel="self"/>
  <updated>2024-03-05T00:00:00+00:00</updated>
  <author>
    <name>End Point Dev</name>
  </author>
  
    <entry>
      <title>Making a Loading Spinner with tkinter</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2024/03/making-a-loading-spinner-with-tkinter/"/>
      <id>https://www.endpointdev.com/blog/2024/03/making-a-loading-spinner-with-tkinter/</id>
      <published>2024-03-05T00:00:00+00:00</published>
      <author>
        <name>Matt Vollrath</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2024/03/making-a-loading-spinner-with-tkinter/spiral-stairs.webp&#34; alt=&#34;An overhead shot of a carpeted spiral staircase, with spiraling railings on either side. The staircase is cut off at the bottom by a wall, so that only half of the circle of stairs is visible. The stairs are enclosed by a semicircular wall, and lit by sunlight streaming through a window on the left. On the right is a window whose view is filled with green leaves.&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Seth Jensen, 2023. --&gt;
&lt;p&gt;When you need a loading spinner, you really need a loading spinner. Interested in putting something on the screen without installing a pile of dependencies, I reached deep into the toolbox of the Python standard library, dug around a bit, and pulled out the &lt;a href=&#34;https://docs.python.org/3/library/tkinter.html&#34;&gt;tkinter&lt;/a&gt; module.&lt;/p&gt;
&lt;p&gt;The tkinter module is an interface to the venerable Tcl/Tk GUI toolkit, a cross-platform suite for creating user interfaces in the style of whatever operating system you run it on. It&amp;rsquo;s the only built-in GUI toolkit in Python, but there are many worthy alternatives available (see the end of the post for a list).&lt;/p&gt;
&lt;p&gt;Here I&amp;rsquo;ll demonstrate how to make a loading spinnner with tkinter on Ubuntu 22.04. It should work on any platform that runs Python, with some variations when setting up the system for it.&lt;/p&gt;
&lt;h3 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;My vision for the loading spinner is some spinning dots and a logo, since this is such a convenient branding opportunity. To accomplish this we&amp;rsquo;ll be extending tkinter with Pillow&amp;rsquo;s ImageTk capability, which can load a PNG with transparency.&lt;/p&gt;
&lt;p&gt;To produce that PNG with transparency, first we may need to rasterize an SVG file, because wise designers work in vectors. This is made trivial by &lt;a href=&#34;https://inkscape.org/&#34;&gt;Inkscape&lt;/a&gt;, a free and complete vector graphics tool:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ inkscape logo.svg -o logo.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With the logo in hand, we can move on to setting up our dependencies. Ubuntu&amp;rsquo;s python3 distribution doesn&amp;rsquo;t include tkinter by default, so we&amp;rsquo;ll need to install it explicitly, along with Pillow&amp;rsquo;s separate ImageTk support:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo apt install python3-tk python3-pil.imagetk&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This may occupy up to 75MB, if Python was already installed. This was still the smallest apt footprint of all of the Python GUI libraries in consideration. Pygame was also a strong contender.&lt;/p&gt;
&lt;h3 id=&#34;code&#34;&gt;Code&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s start with something simple: putting the logo on the screen.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;PIL&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; Image, ImageTk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;tkinter&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; BOTH, Canvas, Tk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Desired dimensions of our window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WIDTH, HEIGHT = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;__name__&lt;/span&gt; == &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create the root window object.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root = Tk()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create a canvas for drawing our graphics.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas = Canvas(root, width=WIDTH, height=HEIGHT, background=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;black&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Fill the entire window with the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.pack(fill=BOTH, expand=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Load the logo PNG with regular PIL.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = Image.open(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;logo.png&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Convert the logo to an ImageTk PhotoImage.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_pi = ImageTk.PhotoImage(logo_img)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Add our logo image to the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.create_image(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        WIDTH / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        HEIGHT / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image=logo_pi,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Run the tkinter main loop.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root.mainloop()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This puts the logo in the center of a window, but the logo may be too large or small. Let&amp;rsquo;s scale it according to the window dimensions, let&amp;rsquo;s say to about ⅔ of the width so we have some room for spinning dots:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;PIL&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; Image, ImageTk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;tkinter&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; BOTH, Canvas, Tk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Desired dimensions of our window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WIDTH, HEIGHT = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;__name__&lt;/span&gt; == &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create the root window object.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root = Tk()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create a canvas for drawing our graphics.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas = Canvas(root, width=WIDTH, height=HEIGHT, background=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;black&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Fill the entire window with the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.pack(fill=BOTH, expand=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Load the logo PNG with regular PIL.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = Image.open(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;logo.png&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Resize the logo to about 2/3 the window width.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scaled_w = &lt;span style=&#34;color:#038&#34;&gt;round&lt;/span&gt;(WIDTH * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.6&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scaled_h = &lt;span style=&#34;color:#038&#34;&gt;round&lt;/span&gt;(scaled_w / (logo_img.width / logo_img.height))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = logo_img.resize((scaled_w, scaled_h), Image.LANCZOS)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Convert the logo to an ImageTk PhotoImage.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_pi = ImageTk.PhotoImage(logo_img)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Add our logo image to the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.create_image(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        WIDTH / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        HEIGHT / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image=logo_pi,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Run the tkinter main loop.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root.mainloop()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That&amp;rsquo;s better. Now let&amp;rsquo;s add the promised spinning dots. We&amp;rsquo;ll draw some ovals on the canvas and modify our main loop to animate them:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;math&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;PIL&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; Image, ImageTk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;tkinter&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; BOTH, Canvas, Tk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Desired dimensions of our window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WIDTH, HEIGHT = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Coordinates of the center.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CENTER_X, CENTER_Y = WIDTH / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;, HEIGHT / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# How many spinning dots we want.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NUM_DOTS = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;__name__&lt;/span&gt; == &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create the root window object.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root = Tk()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create a canvas for drawing our graphics.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas = Canvas(root, width=WIDTH, height=HEIGHT, background=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;black&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Fill the entire window with the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.pack(fill=BOTH, expand=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Load the logo PNG with regular PIL.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = Image.open(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;logo.png&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Resize the logo to about 2/3 the window width.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scaled_w = &lt;span style=&#34;color:#038&#34;&gt;round&lt;/span&gt;(WIDTH * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.6&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scaled_h = &lt;span style=&#34;color:#038&#34;&gt;round&lt;/span&gt;(scaled_w / (logo_img.width / logo_img.height))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = logo_img.resize((scaled_w, scaled_h), Image.LANCZOS)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Convert the logo to an ImageTk PhotoImage.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_pi = ImageTk.PhotoImage(logo_img)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Add our logo image to the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.create_image(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        CENTER_X,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        CENTER_Y,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image=logo_pi,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Radius in pixels of a single dot.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dot_radius = WIDTH * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.05&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Radius of the ring of dots from the center of the window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dots_radius = WIDTH / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt; - dot_radius * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Helper function to calculate dot position on each update.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;get_dot_coords&lt;/span&gt;(n: &lt;span style=&#34;color:#038&#34;&gt;int&lt;/span&gt;, t: &lt;span style=&#34;color:#038&#34;&gt;float&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Get the x0, y0, x1, y1 coords of dot at index &amp;#39;n&amp;#39; at time &amp;#39;t&amp;#39;.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        angle = (n / NUM_DOTS) * math.pi * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt; + t
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        x = math.cos(angle) * dots_radius + CENTER_X
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        y = math.sin(angle) * dots_radius + CENTER_Y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; x - dot_radius, y - dot_radius, x + dot_radius, y + dot_radius
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create all the dots.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    t0 = time.monotonic()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; n &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;range&lt;/span&gt;(NUM_DOTS):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        coords = get_dot_coords(n, t0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        canvas.create_oval(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            *coords,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            fill=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;#888888&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            width=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;,  &lt;span style=&#34;color:#888&#34;&gt;# Border width.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            tags=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;dot_&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;n&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Set up a custom main loop to animate the moving dots.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Check the time of this update.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t = time.monotonic()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; n &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;range&lt;/span&gt;(NUM_DOTS):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;# Get the desired coords for this dot at this time.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            coords = get_dot_coords(n, t)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;# Move the dot on the canvas, finding it by its tag.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            canvas.coords(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;dot_&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;n&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                *coords,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Call the required tkinter update function.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        root.update()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Attempt to stabilize the timing of this loop by targeting 60Hz.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;while&lt;/span&gt; t0 &amp;lt; t:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            t0 += &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt; / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        time.sleep(t0 - t)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You may notice that the dots don&amp;rsquo;t look all that great. There&amp;rsquo;s no anti-aliasing when drawing shape primitives in tkinter, so the edges look jagged compared to our well-scaled logo image. One hack is to layer slightly larger and dimmer shapes under each object, which you might do like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;math&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;PIL&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; Image, ImageTk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;tkinter&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; BOTH, Canvas, Tk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Desired dimensions of our window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WIDTH, HEIGHT = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Coordinates of the center.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CENTER_X, CENTER_Y = WIDTH / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;, HEIGHT / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# How many spinning dots we want.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NUM_DOTS = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Colors for each layer of fake anti-aliasing around each dot.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Must be in order from back to front.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;COLORS = [&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;#888888&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;#BBBBBB&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;#FFFFFF&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;__name__&lt;/span&gt; == &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create the root window object.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root = Tk()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create a canvas for drawing our graphics.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas = Canvas(root, width=WIDTH, height=HEIGHT, background=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;black&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Fill the entire window with the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.pack(fill=BOTH, expand=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Load the logo PNG with regular PIL.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = Image.open(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;logo.png&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Resize the logo to about 2/3 the window width.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scaled_w = &lt;span style=&#34;color:#038&#34;&gt;round&lt;/span&gt;(WIDTH * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.6&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scaled_h = &lt;span style=&#34;color:#038&#34;&gt;round&lt;/span&gt;(scaled_w / (logo_img.width / logo_img.height))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_img = logo_img.resize((scaled_w, scaled_h), Image.LANCZOS)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Convert the logo to an ImageTk PhotoImage.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logo_pi = ImageTk.PhotoImage(logo_img)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Add our logo image to the canvas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    canvas.create_image(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        CENTER_X,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        CENTER_Y,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image=logo_pi,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Radius in pixels of a single dot.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dot_radius = WIDTH * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.05&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Radius of the ring of dots from the center of the window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dots_radius = WIDTH / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt; - dot_radius * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Helper function to calculate dot position on each update.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;get_dot_coords&lt;/span&gt;(n: &lt;span style=&#34;color:#038&#34;&gt;int&lt;/span&gt;, t: &lt;span style=&#34;color:#038&#34;&gt;float&lt;/span&gt;, c: &lt;span style=&#34;color:#038&#34;&gt;int&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Get the x0, y0, x1, y1 coords of dot at index &amp;#39;n&amp;#39; at time &amp;#39;t&amp;#39;.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;        Inflate the radius by color index &amp;#39;c&amp;#39;.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        angle = (n / NUM_DOTS) * math.pi * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt; + t
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        x = math.cos(angle) * dots_radius + CENTER_X
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        y = math.sin(angle) * dots_radius + CENTER_Y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Invert the color index and add to the radius.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        radius = dot_radius + (&lt;span style=&#34;color:#038&#34;&gt;len&lt;/span&gt;(COLORS) - c) * &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.75&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;#radius = dot_radius + c&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; x - radius, y - radius, x + radius, y + radius
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Create all the dots.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    t0 = time.monotonic()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; c, color &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;enumerate&lt;/span&gt;(COLORS):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; n &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;range&lt;/span&gt;(NUM_DOTS):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            coords = get_dot_coords(n, t0, c)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            canvas.create_oval(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                *coords,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                fill=color,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                width=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;,  &lt;span style=&#34;color:#888&#34;&gt;# Border width.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                tags=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;dot_&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;c&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;_&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;n&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Set up a custom main loop to animate the moving dots.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Check the time of this update.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t = time.monotonic()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; c, color &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;enumerate&lt;/span&gt;(COLORS):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; n &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;range&lt;/span&gt;(NUM_DOTS):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#888&#34;&gt;# Get the desired coords for this dot at this time.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                coords = get_dot_coords(n, t, c)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#888&#34;&gt;# Move the dot on the canvas, finding it by its tag.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                canvas.coords(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;dot_&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;c&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;_&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;n&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    *coords,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Call the required tkinter update function.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        root.update()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888&#34;&gt;# Attempt to stabilize the timing of this loop by targeting 60Hz.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;while&lt;/span&gt; t0 &amp;lt; t:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            t0 += &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt; / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        time.sleep(t0 - t)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The fake anti-aliasing was a fun exercise, but for this use case you&amp;rsquo;ll probably get better-looking results out of scaling a PNG asset like we did the logo.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2024/03/making-a-loading-spinner-with-tkinter/spinner.webp&#34; alt=&#34;A screenshot of the loading spinner. In the center is a logo reading &amp;ldquo;VisionPort&amp;rdquo;, with the O replaced by a globe with a locator icon in it. Surrounding the logo are animated dots rotating in a circle.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;resources&#34;&gt;Resources&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;re interested in learning more about tkinter, see also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.python.org/3/library/tkinter.html&#34;&gt;Python tkinter docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/index.html&#34;&gt;The tkinter reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other Python GUI/graphics toolkits you might consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.pygame.org/news&#34;&gt;Pygame&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pyglet.org/&#34;&gt;Pyglet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://doc.qt.io/qtforpython-6/&#34;&gt;PySide6&lt;/a&gt; (official Qt binding)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://riverbankcomputing.com/software/pyqt/intro&#34;&gt;PyQt&lt;/a&gt; (unofficial Qt binding)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://wxpython.org/index.html&#34;&gt;wxPython&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Automating reading the screen and interacting with GUI programs on X Window System</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/03/automate-read-screen-interact-gui-x11/"/>
      <id>https://www.endpointdev.com/blog/2022/03/automate-read-screen-interact-gui-x11/</id>
      <published>2022-03-10T00:00:00+00:00</published>
      <author>
        <name>Constante “Tino” Gonzalez</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/03/automate-read-screen-interact-gui-x11/20220216_224654.webp&#34; alt=&#34;Metal tower with cables in front of overcast sky and muted sun&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Jon Jensen --&gt;
&lt;p&gt;A while back, Google Earth made some changes to the layer select menu in the sidebar, which broke a program that toggles the 3D imagery on VisionPort systems. These run the X Window System (also known as X11, or just X) on Ubuntu Linux.&lt;/p&gt;
&lt;p&gt;In looking for a workaround, I found that shell scripts can fully interact with GUI apps using the &lt;code&gt;xdotool&lt;/code&gt;, &lt;code&gt;xwd&lt;/code&gt;, and &lt;code&gt;convert&lt;/code&gt; commands. This script would send a series of keystrokes to open the sidebar, navigate the layers menu, and toggle the box for the 3D buildings layer.&lt;/p&gt;
&lt;p&gt;Changing the series of keystrokes to match the new number of layers should have fixed the issue, but there was more to this script. The next part of the script would take a screenshot, crop the checkbox, and compare it to saved files of other cropped boxes. Fixing this part of the script required correcting the positions of the captures and replacing the reference files with ones that pictured the updated Google Earth checkbox states.&lt;/p&gt;
&lt;p&gt;Here I will explain how the script works and how we changed it so that it no longer needs these reference files and ultimately runs faster.&lt;/p&gt;
&lt;h3 id=&#34;overview-of-how-the-script-works&#34;&gt;Overview of how the script works&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;xwd&lt;/code&gt; takes a screenshot of a window on the screen.
&lt;code&gt;convert&lt;/code&gt; transforms the pixel data in the image into lines of text with location and color that we can easily search and read with &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;sed&lt;/code&gt;, or similar.
&lt;code&gt;xdotool&lt;/code&gt; interacts with GUI windows. It can find, focus, and send keystrokes and mouse commands, among other things.&lt;/p&gt;
&lt;h3 id=&#34;preparing&#34;&gt;Preparing&lt;/h3&gt;
&lt;p&gt;To illustrate, let’s make a simple case of looking for a button and clicking on it from the terminal. We will skip making a script for this example as most of this can be accomplished on a single command line with &lt;code&gt;xdotool&lt;/code&gt;. The commands here are for the Ubuntu operating system and may be a little different on other systems.&lt;/p&gt;
&lt;p&gt;If you would like to try it as you read along, you will need an image editor to quickly look up pixel positions and colors. GIMP works great and is pictured in this example.&lt;/p&gt;
&lt;p&gt;You will also need to install some packages. On a terminal:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt install xdotool x11-apps imagemagick&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;working-through-an-example&#34;&gt;Working through an example&lt;/h3&gt;
&lt;p&gt;1. To know which window to interact with, xdotool needs to know the window’s name. So let’s open a browser and navigate to endpointdev.com and then note the page title in the browser tab, “Secure Business Solutions”:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/03/automate-read-screen-interact-gui-x11/1-scr-read.webp&#34; alt=&#34;Screenshot of endpointdev.com website home page loaded in a browser&#34;&gt;&lt;/p&gt;
&lt;p&gt;2. On the terminal:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;xdotool search &amp;#34;secure business solutions&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Defaulting to search window name, class, and classname
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;69206019&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This reminds us that it can search for windows in many ways and returns a window ID, in this example 69206019. Search results also get stored on the “window stack” and can be referenced as &lt;code&gt;%1&lt;/code&gt;, &lt;code&gt;%2&lt;/code&gt;, and so on.&lt;/p&gt;
&lt;p&gt;3. Use this ID in the next terminal command. If you have more than one ID, you may need to refine your search:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;xwd -id &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;69206019&lt;/span&gt; -out endpointdev.xwd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;xwd&lt;/code&gt; creates a screenshot of the window that we want to see and saves it to the filename passed with the &lt;code&gt;-out&lt;/code&gt; argument.&lt;/p&gt;
&lt;p&gt;4. Then run a &lt;code&gt;convert&lt;/code&gt; command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;convert endpointdev.xwd endpointdev.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;By converting the image to text we can use any text tools like &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;cut&lt;/code&gt;, &lt;code&gt;diff&lt;/code&gt;, or &lt;code&gt;sed&lt;/code&gt; to find colors and coordinates on the images. We just need to know how to read it. The first few lines of the endpointdev.txt file look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# ImageMagick pixel enumeration: 1156,638,65535,srgb
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;0,0: (11565,11565,11565)  #2D2D2D  srgb(45,45,45)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1,0: (10537,10537,10537)  #292929  grey16
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2,0: (4369,4369,4369)  #111111  srgb(17,17,17)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;3,0: (0,0,0)  #000000  black
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;4,0: (0,0,0)  #000000  black
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;5,0: (0,0,0)  #000000  black&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Each line represents a pixel:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first two numbers are the x and y positions,&lt;/li&gt;
&lt;li&gt;in parentheses are the decimal color values per RGB channel,&lt;/li&gt;
&lt;li&gt;after &lt;code&gt;#&lt;/code&gt; is the HTML RGB color value which we will be using in our example,&lt;/li&gt;
&lt;li&gt;and last the &lt;code&gt;srgb&lt;/code&gt; code or name for the color.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can use any of these color codes for matching. To decode these color codes visually we use an image editor.&lt;/p&gt;
&lt;p&gt;5. Open the &lt;code&gt;endpointdev.xwd&lt;/code&gt; file that we created in an image editor. Here we will use GIMP:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/03/automate-read-screen-interact-gui-x11/2-scr-read.webp&#34; alt=&#34;Screenshot of GIMP image editor open with a screenshot of endpointdev.com website home page loaded in a browser&#34;&gt;&lt;/p&gt;
&lt;p&gt;6. Select the zoom tool (see the mouse pointer in the screenshot above to know which that is). Use it to draw a box around the VisionPort button or something else that we want our script to “see” and zoom in on it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/03/automate-read-screen-interact-gui-x11/3-scr-read.webp&#34; alt=&#34;Screenshot of GIMP image editor zoom tool selected on a screenshot of a tiny part of the endpointdev.com website with a VisionPort link&#34;&gt;&lt;/p&gt;
&lt;p&gt;7. Next, use the color picker tool (check the mouse pointer in the screenshot above for this tool) and click on the circle, or any other spot you want to pick for your script:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/03/automate-read-screen-interact-gui-x11/4-scr-read.webp&#34; alt=&#34;Screenshot of GIMP image editor color picker tool selected on a screenshot of a tiny part of the endpointdev.com website with a VisionPort link&#34;&gt;&lt;/p&gt;
&lt;p&gt;8. Notice by the pointer above how the color picker box changed colors. Now clicking on this box brings up a new window:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/03/automate-read-screen-interact-gui-x11/5-scr-read.webp&#34; alt=&#34;Screenshot of GIMP image editor &amp;ldquo;Change Foreground Color&amp;rdquo; dialog box&#34;&gt;&lt;/p&gt;
&lt;p&gt;Here we can find the HTML notation of the color we selected: &lt;code&gt;00ffcc&lt;/code&gt; (see the pointer in the screenshot above). We’ll call this “VisionPort button green”.&lt;/p&gt;
&lt;p&gt;In my experience even things that look like the same color can have a unique pixel caused by a different shade on an edge or something else. Our old script checked a 25×25 pixel image to guarantee what it had was a check mark, but finding one of these colors unique enough to use as a key ensures that the check mark is next to it, so we can check the state on a single pixel too.&lt;/p&gt;
&lt;p&gt;If one pixel is not unique enough we can look for a pattern of pixels and still store them as variables on the script to avoid saving and comparing files.&lt;/p&gt;
&lt;p&gt;9. With this HTML hex RGB color code we go back to the terminal and search our file for it. Use capital letters for the color or else &lt;code&gt;grep -i&lt;/code&gt; to make it case-insensitive:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;grep 00FFCC endpointdev.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will return a list of pixels with this color, such as:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;827,107: (0,65535,52428)  #00FFCC  srgb(0,255,204)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;839,107: (0,65535,52428)  #00FFCC  srgb(0,255,204)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;853,107: (0,65535,52428)  #00FFCC  srgb(0,255,204)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;878,107: (0,65535,52428)  #00FFCC  srgb(0,255,204)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;883,107: (0,65535,52428)  #00FFCC  srgb(0,255,204)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;10. For our purpose we can use any of those, so let’s try the first result. In the terminal run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;xdotool windowfocus &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;69206019&lt;/span&gt; mousemove &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;827&lt;/span&gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;107&lt;/span&gt; click &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Your browser should then follow the link to visionport.com and load that website&amp;rsquo;s home page in your browser:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/03/automate-read-screen-interact-gui-x11/6-scr-read.webp&#34; alt=&#34;Screenshot of visionport.com website loaded in a web browser&#34;&gt;&lt;/p&gt;
&lt;p&gt;Did it work for you too? 🎉&lt;/p&gt;
&lt;p&gt;That is all we need to build a script that interacts with GUI apps. If you would like to practice, take another screenshot and use xdotool to look for and click the “contact” button. 😁&lt;/p&gt;
&lt;h3 id=&#34;how-it-works-in-a-little-more-detail&#34;&gt;How it works in a little more detail&lt;/h3&gt;
&lt;p&gt;The last xdotool command chain&amp;rsquo;s steps are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;windowfocus 69206019&lt;/code&gt; selects the ID that we searched for before,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mousemove&lt;/code&gt; moves the mouse pointer to the specified absolute coordinates, in this case the first pixel that matched the VisionPort button green color in the &lt;code&gt;endpointdev.txt&lt;/code&gt; file,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;click 1&lt;/code&gt; sends a mouse left click at the mouse’s current position.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Updating our script to use this instead of the series of keystrokes took some investigation and testing but made it faster in the end.&lt;/p&gt;
&lt;p&gt;One of the issues to overcome was that the VisionPort custom layers capture xdotool&amp;rsquo;s window and mouse commands, so to target a specific window on the VisionPort, we have to offset the coordinates of our commands according to the position of the window on the screen.&lt;/p&gt;
&lt;h3 id=&#34;chaining-xdotool-actions&#34;&gt;Chaining xdotool actions&lt;/h3&gt;
&lt;p&gt;Here is another example of chaining several xdotool actions in a single command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;xdotool search &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;secure business solutions&amp;#34;&lt;/span&gt; windowfocus key Alt+F4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That should close the browser window on most systems. If any of these commands failed, try replacing &lt;code&gt;windowfocus&lt;/code&gt; with &lt;code&gt;windowactivate&lt;/code&gt;; some xdotool commands depend on system support. Check xdotool’s manual for more options.&lt;/p&gt;
&lt;p&gt;Command chain steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;search &amp;lt;name&amp;gt;&lt;/code&gt;: Finds windows with that name, and stores the results in the “window stack” memory.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;windowfocus&lt;/code&gt;: Selects a window. Defaults to the first window from xdotool’s window stack; no need to supply the window ID if following a search.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;key Alt+F4&lt;/code&gt;: Sends the Alt+F4 keypress to the selected window. The &lt;code&gt;+&lt;/code&gt; here means at the same time. Use spaces to separate a list of keys. By default there is a delay between keystrokes, and that is configurable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use the standard Unix &lt;code&gt;sleep &amp;lt;seconds&amp;gt;&lt;/code&gt; in between other xdotool commands that may need delays.&lt;/p&gt;
&lt;p&gt;xdotool takes a list of commands naturally, which can even be stored and read directly from text files. They can also be executed directly by making the file executable (with &lt;code&gt;chmod +x&lt;/code&gt;) and setting the shebang line (first line of the file) to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#!/usr/bin/xdotool&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Such executable scripts are commonly named using a &lt;code&gt;.xdo&lt;/code&gt; extension.&lt;/p&gt;
&lt;h3 id=&#34;fine-tuning&#34;&gt;Fine-tuning&lt;/h3&gt;
&lt;p&gt;While we can convert the whole screenshot to text and search on it like we did in the example above, the files are large, so cropping a smaller section before converting to text is recommended.&lt;/p&gt;
&lt;p&gt;We can crop a section, save it to a file and compare it to another file, like our script did. We can also crop an image to 1 pixel wide across a menu bar if that’s what we’re aiming for, or a column of icons in our case, to find all the button positions without much overhead. Doing this instead of only checking the area hard-coded for a check box gave our script the ability to find the check box based on the icon next to it when changing positions due to other tabs expanding on the menu.&lt;/p&gt;
&lt;p&gt;It is also possible to use the multiprocessing features of shell scripts: When working on multiple window screenshots or crops, we can have them all run at the same time using the &lt;code&gt;&amp;amp;&lt;/code&gt; operator at the end of the commands, then use the &lt;code&gt;wait&lt;/code&gt; command to let them all finish before performing the text searches. These are very fast on small cropped files.&lt;/p&gt;
&lt;h3 id=&#34;learning-more&#34;&gt;Learning more&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&#34;https://man.cx/xdotool&#34;&gt;xdotool documentation&lt;/a&gt; has information on other ways to identify and interact with windows and is a great place to go to learn more.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Optimizing media delivery with Cloudinary</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/03/optimizing-image-delivery-with-cloudinary/"/>
      <id>https://www.endpointdev.com/blog/2022/03/optimizing-image-delivery-with-cloudinary/</id>
      <published>2022-03-01T00:00:00+00:00</published>
      <author>
        <name>Juan Pablo Ventoso</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/03/optimizing-image-delivery-with-cloudinary/la-cumbrecita-202201.webp&#34; alt=&#34;Beautiful cloudy mountain scene with river flowing by lush banks with people swimming, relaxing, and walking towards multistory buildings&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Juan Pablo Ventoso --&gt;
&lt;p&gt;I remember how we needed to deal with different image formats and sizes years ago: From using the WordPress-style approach of automatically saving different resolutions on the server when uploading a picture, to using a PHP script to resize or crop images on the fly and return the result as a response to the frontend. Of course, many of those approaches were expensive, and not fully optimized for different browsers or device sizes.&lt;/p&gt;
&lt;p&gt;With those experiences in mind, it was a nice surprise for me to discover &lt;a href=&#34;https://cloudinary.com/&#34;&gt;Cloudinary&lt;/a&gt; when working on a new project a couple of months ago. It&amp;rsquo;s basically a cloud service that saves and delivers media content with a lot of transformations and management options for us to use. &lt;a href=&#34;https://cloudinary.com/pricing&#34;&gt;There is a free version&lt;/a&gt; with a usage limit: Up to 25K transformations or 25 GB of storage/​bandwidth, which should be enough for most non-enterprise websites. The cheapest paid service is $99 per month.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a list of the image features we used on that project. I know they offer many other things that can be used as well, but I think this is a good start for anyone who hasn&amp;rsquo;t used this service yet:&lt;/p&gt;
&lt;h3 id=&#34;resizing-and-cropping&#34;&gt;Resizing and cropping&lt;/h3&gt;
&lt;p&gt;When you make a request for an image, you can instruct the Cloudinary API to &lt;a href=&#34;https://cloudinary.com/documentation/resizing_and_cropping&#34;&gt;retrieve it with a given size&lt;/a&gt;, which will trigger a transformation on their end before delivering the content. You can also use a cropping method: fill, fill with padding, scale down, etc.&lt;/p&gt;
&lt;h3 id=&#34;gravity-position&#34;&gt;Gravity position&lt;/h3&gt;
&lt;p&gt;When we specify a &lt;a href=&#34;https://cloudinary.com/documentation/resizing_and_cropping#control_gravity&#34;&gt;gravity position&lt;/a&gt; to crop an image, the service will keep the area of the image we decide to use as the focal point. We can choose a corner (for example, top left), but also—and this is probably one of the most interesting capabilities on this service—we can specify &lt;a href=&#34;https://cloudinary.com/documentation/transformation_reference#g_special_position&#34;&gt;&amp;ldquo;special positions&amp;rdquo;&lt;/a&gt;: By using machine learning, we can instruct Cloudinary to use face detection, or even focus on other objects, like an animal or a flower in the picture.&lt;/p&gt;
&lt;h3 id=&#34;automatic-format&#34;&gt;Automatic format&lt;/h3&gt;
&lt;p&gt;Another cool feature is the &lt;a href=&#34;https://cloudinary.com/documentation/transformation_reference#f_auto&#34;&gt;automatic format&lt;/a&gt;, which will use your request headers to find the most efficient picture format for your browser type and version. For example, if the browser supports it, Cloudinary will return the image in WebP format, which is generally more efficient than standard JPEG, as End Point CTO Jon Jensen demonstrates on his recent &lt;a href=&#34;https://www.endpointdev.com/blog/2022/02/webp-heif-avif-jpegxl/&#34;&gt;blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/03/optimizing-image-delivery-with-cloudinary/image-response.jpg&#34; alt=&#34;Screenshot of Chrome browser dev tools showing network response for a WebP image&#34;&gt;&lt;br&gt;
Automatic format in action: Returning a WebP image in Chrome&lt;/p&gt;
&lt;h3 id=&#34;other-features&#34;&gt;Other features&lt;/h3&gt;
&lt;p&gt;There are many other options for us to choose when querying their API, like setting up a default placeholder when we don’t have an image, applying color transformations, removing red eyes, among other things. The &lt;a href=&#34;https://cloudinary.com/documentation/transformation_reference&#34;&gt;Transformation reference page&lt;/a&gt; on their documentation section is a great resource.&lt;/p&gt;
&lt;h3 id=&#34;nuxtjs-integration&#34;&gt;NuxtJS integration&lt;/h3&gt;
&lt;p&gt;The project I mentioned above was a &lt;a href=&#34;https://nuxtjs.org/&#34;&gt;NuxtJS&lt;/a&gt; application with a &lt;a href=&#34;https://nodejs.org/&#34;&gt;Node.js&lt;/a&gt; backend. And since there&amp;rsquo;s a &lt;a href=&#34;https://cloudinary.nuxtjs.org/&#34;&gt;NuxtJS module for Cloudinary&lt;/a&gt;, it made sense to use it instead of building the queries to the API from scratch.&lt;/p&gt;
&lt;p&gt;The component works great, except for one bug that we found that didn&amp;rsquo;t allow us to fully use their image component with server-side rendering enabled. Between that drawback and some issues trying to use the lazy loading setting, we ended up creating a Vue component ourselves that used a standard image tag instead. But we still used their component to generate most of the API calls and render the results.&lt;/p&gt;
&lt;p&gt;Below is an example of using the Cloudinary Image component on a Vue template:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;template&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;cld-image&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#369&#34;&gt;:public-id&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;publicId&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#369&#34;&gt;width&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;200&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#369&#34;&gt;height&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;200&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#369&#34;&gt;crop&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;fill&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#369&#34;&gt;gravity&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;auto:subject&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#369&#34;&gt;radius&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;max&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#369&#34;&gt;fetchFormat&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;auto&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#369&#34;&gt;quality&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;auto&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#369&#34;&gt;alt&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;An image example with Cloudinary&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;template&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;alternatives&#34;&gt;Alternatives&lt;/h3&gt;
&lt;p&gt;Of course, Cloudinary is not the only image processing and CDN company out there: There are other companies offering similar services, like &lt;a href=&#34;https://www.cloudflare.com/products/cloudflare-images/&#34;&gt;Cloudflare images&lt;/a&gt;, &lt;a href=&#34;https://www.cloudimage.io/&#34;&gt;Cloudimage&lt;/a&gt;, or &lt;a href=&#34;https://imagekit.io/&#34;&gt;imagekit.io&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Do you know any other good alternatives, or have you used any other Cloudinary feature that is not listed here? Feel free to add a comment below!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Image compression: WebP presets, HEIC, AVIF, JPEG XL</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/02/webp-heif-avif-jpegxl/"/>
      <id>https://www.endpointdev.com/blog/2022/02/webp-heif-avif-jpegxl/</id>
      <published>2022-02-15T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/02/webp-heif-avif-jpegxl/20211223-224644-sm.webp&#34; alt=&#34;Rubble of a demolished house in front of a guilty-looking Caterpillar excavator&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Jon Jensen --&gt;
&lt;p&gt;How time flies. Eight years ago I wrote the blog post &lt;a href=&#34;/blog/2014/01/webp-images-experiment-on-end-point/&#34;&gt;WebP images experiment on End Point website&lt;/a&gt; to describe and demonstrate how the WebP image format can store an equivalent-quality image in a much smaller file size than the older JPEG, PNG, and GIF formats.&lt;/p&gt;
&lt;p&gt;My WebP examples there were 17–23% of the JPEGs they came from, or about 5–6× smaller. While experimenting with higher levels of compression, I found that WebP tends to leave less-noticeable artifacts than JPEG does.&lt;/p&gt;
&lt;p&gt;The main drawback at the time was that, among major browsers, only Chrome and Opera supported WebP, and back then, Chrome was far less popular than it is now.&lt;/p&gt;
&lt;h3 id=&#34;can-i-use-it&#34;&gt;Can I use it?&lt;/h3&gt;
&lt;p&gt;Since Apple&amp;rsquo;s iOS 14 and macOS 11 (Big Sur) became available in late 2020, the WebP image format now works in all the currently supported major operating systems and browsers: Linux, Windows, and macOS, running Chromium, Chrome, Brave, Edge, Opera, Firefox, and Safari. You can see the specifics at the ever-useful site &lt;a href=&#34;https://www.caniuse.com/webp&#34;&gt;&amp;ldquo;Can I use&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It only took about 10 years! 😁&lt;/p&gt;
&lt;p&gt;So for you who are hosting websites, all your site visitors can see WebP images and animations except those vanishing few using Internet Explorer (now long past its end of support by Microsoft and dangerous to use), and people (or their organizations) who intentionally do not allow their older browsers and operating systems to be updated.&lt;/p&gt;
&lt;p&gt;That means you may want to continue using JPEG, PNG, and/or GIF images in places crucial for rendering your main site features, for people to be able to understand the main things your site is trying to communicate.&lt;/p&gt;
&lt;p&gt;But for any images that are less essential and primarily making it prettier, or where you don&amp;rsquo;t mind suggesting that users of old browsers upgrade to see them, WebP can now be your default image format.&lt;/p&gt;
&lt;h3 id=&#34;how-do-i-create-webp-images&#34;&gt;How do I create WebP images?&lt;/h3&gt;
&lt;p&gt;Mobile phones and digital cameras typically save JPEG, HEIF, or raw (uncompressed) images. Some stock photography collections offer WebP downloads, but many still use JPEG.&lt;/p&gt;
&lt;p&gt;So in many cases you won&amp;rsquo;t be starting with a WebP image, and you&amp;rsquo;ll convert some other image to WebP, likely after cropping, scaling, and other adjustments.&lt;/p&gt;
&lt;p&gt;GIMP (GNU Image Manipulation Program) supports WebP images since about 2017, and Adobe Photoshop does not natively but can use the &lt;a href=&#34;https://developers.google.com/speed/webp/docs/webpshop&#34;&gt;free WebPShop plugin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The oldest way to convert images to WebP, and still very useful for batch processing or fine-tuning, is &lt;a href=&#34;https://developers.google.com/speed/webp&#34;&gt;Google&amp;rsquo;s WebP converter &amp;ldquo;cwebp&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;cwebp-settings&#34;&gt;cwebp settings&lt;/h3&gt;
&lt;p&gt;With the power &amp;ldquo;cwebp&amp;rdquo; offers comes some complexity, but it is mostly harmless.&lt;/p&gt;
&lt;p&gt;Run this to see its many options:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cwebp -longhelp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or read the same thing in its &lt;a href=&#34;https://developers.google.com/speed/webp/docs/cwebp&#34;&gt;online documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There among other things you will see the &lt;code&gt;-z&lt;/code&gt; option which activates preset features for lossless encoding, with an integer level chosen from 0 to 9 where 0 is fastest but compresses less and 9 is slowest but compresses better. Use this to replace PNG files when you want no degradation of the image at all.&lt;/p&gt;
&lt;p&gt;The documentation also shows the useful &lt;code&gt;-preset&lt;/code&gt; and &lt;code&gt;-hint&lt;/code&gt; options for lossy compression similar to what JPEG does, but better:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-preset &amp;lt;string&amp;gt; ....... preset setting, one of:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                          default, photo, picture,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                          drawing, icon, text
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-hint &amp;lt;string&amp;gt; ......... specify image characteristics hint,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                         one of: photo, picture or graph&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The meaning of a few of those terms, especially &amp;ldquo;photo&amp;rdquo; and &amp;ldquo;picture&amp;rdquo;, was not clear to me and not defined elsewhere in the documentation that I could see.&lt;/p&gt;
&lt;p&gt;To find that out I had to make a quick trip into the source code, and there are comments explaining each option&amp;rsquo;s use case a bit:&lt;/p&gt;
&lt;p&gt;The comments for the &lt;a href=&#34;https://chromium.googlesource.com/webm/libwebp/+/refs/heads/main/src/webp/encode.h#159&#34;&gt;&lt;code&gt;-preset&lt;/code&gt; option&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WEBP_PRESET_PICTURE,  // digital picture, like portrait, inner shot
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WEBP_PRESET_PHOTO,    // outdoor photograph, with natural lighting
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WEBP_PRESET_DRAWING,  // hand or line drawing, with high-contrast details
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WEBP_PRESET_ICON,     // small-sized colorful images
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WEBP_PRESET_TEXT      // text-like&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And the comments for the &lt;a href=&#34;https://chromium.googlesource.com/webm/libwebp/+/refs/heads/main/src/webp/encode.h#88&#34;&gt;&lt;code&gt;-hint&lt;/code&gt; option&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WEBP_HINT_PICTURE,    // digital picture, like portrait, inner shot
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WEBP_HINT_PHOTO,      // outdoor photograph, with natural lighting
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WEBP_HINT_GRAPH,      // Discrete tone image (graph, map-tile etc).&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So in short, &amp;ldquo;cwebp&amp;rdquo; considers a &amp;ldquo;picture&amp;rdquo; to be indoors and close-up, while &amp;ldquo;photo&amp;rdquo; is outdoors and more likely with more distant focus. That&amp;rsquo;s good to know.&lt;/p&gt;
&lt;h3 id=&#34;batch-conversion&#34;&gt;Batch conversion&lt;/h3&gt;
&lt;p&gt;With that in mind, I can convert a pile of screenshots that have been collecting on my computer to refer to later. One kind of screenshots I sometimes take is of video meetings with mostly indoor views of people. I will use the &amp;ldquo;picture&amp;rdquo; preset for those.&lt;/p&gt;
&lt;p&gt;A simple &lt;code&gt;bash&lt;/code&gt; script works well to process many images in a row:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; infile in Screenshot*.png
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#038&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$infile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;base&lt;/span&gt;=&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;$(&lt;/span&gt;basename &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$infile&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt; .png&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cwebp -preset picture -v &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$infile&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt; -o &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$base&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;.webp
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you have many images to convert to WebP and want to do several at the same time to get done faster, you can use &lt;a href=&#34;https://www.gnu.org/software/parallel/&#34;&gt;GNU parallel&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My screenshots when converted from PNG to WebP consistently take about 3% of the original space, 33–35× smaller! And the quality looks about the same. Amazing.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;These screenshots are 2880×1800 pixels, mostly of Google Meet low-bandwidth video streams. The originals of these screenshots don&amp;rsquo;t look particularly good to begin with, with some blurriness. But exactly because of this, there is no reason for me to keep a larger high-quality original here. The much smaller WebP is fine.&lt;/p&gt;
&lt;h3 id=&#34;competitors-to-webp&#34;&gt;Competitors to WebP&lt;/h3&gt;
&lt;p&gt;Other newer image formats have also been in the works for years, chasing some of the same goals. Should we skip WebP and use one of them instead?&lt;/p&gt;
&lt;h4 id=&#34;heic&#34;&gt;HEIC&lt;/h4&gt;
&lt;p&gt;The &lt;a href=&#34;https://en.wikipedia.org/wiki/High_Efficiency_Image_File_Format#HEIC:_HEVC_in_HEIF&#34;&gt;HEIC (High-Efficiency Image Container)&lt;/a&gt; subset of the HEIF (High Efficiency Image File Format) standard uses High Efficiency Video Coding (HEVC, H.265) for storing images with a &lt;code&gt;.heic&lt;/code&gt; suffix.&lt;/p&gt;
&lt;p&gt;Compared to JPEG, HEIC offers the nice advantages of smaller file sizes for the same quality level (roughly half the size of an equivalent JPEG), and animation support (to replace GIF).&lt;/p&gt;
&lt;p&gt;On the downside, HEIC is encumbered by patents that limit its use for major commercial projects, even on devices licensed for consumer use. It is also slower to encode/​decode. And, concerning for archivists, HEIC shows severe visual damage to the entire image if part of the file is corrupted. In contrast, with corrupted JPEG files the visual damage is typically localized to particular smaller square regions, not the entire image.&lt;/p&gt;
&lt;p&gt;HEIC has been used in Apple&amp;rsquo;s operating systems since the 2017 release of the iPhone 7 and iOS 11 and macOS 10.13 (High Sierra). Support was later added to Windows 10, Android 9, and Ubuntu 20.04.&lt;/p&gt;
&lt;p&gt;As of this writing, no major browsers &lt;a href=&#34;https://caniuse.com/heif&#34;&gt;support HEIC natively&lt;/a&gt;, not even Apple&amp;rsquo;s own Safari.&lt;/p&gt;
&lt;p&gt;So for now, HEIC is primarily used by Apple to store photos more efficiently on its mobile devices.&lt;/p&gt;
&lt;h4 id=&#34;avif&#34;&gt;AVIF&lt;/h4&gt;
&lt;p&gt;The &lt;a href=&#34;https://en.wikipedia.org/wiki/AVIF&#34;&gt;AVIF (AV1 Image File Format)&lt;/a&gt; competes with HEIC and, confusingly, uses the same HEIF container file format that HEIC does. That confusion is reduced in practice by its use of the separate file extension &lt;code&gt;.avif&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.caniuse.com/avif&#34;&gt;AVIF is supported&lt;/a&gt; in current Chrome, Firefox, and Opera. Support was added to WebKit in 2021, but it still has not made its way into Safari. It also works in newer VLC, GIMP, Windows, Android, etc.&lt;/p&gt;
&lt;p&gt;Netflix has a &lt;a href=&#34;https://netflixtechblog.com/avif-for-next-generation-image-coding-b1d75675fe4&#34;&gt;very detailed blog post&lt;/a&gt; comparing AVIF to JPEG and showing AVIF&amp;rsquo;s many advantages.&lt;/p&gt;
&lt;h4 id=&#34;jpeg-xl&#34;&gt;JPEG XL&lt;/h4&gt;
&lt;p&gt;A semi-compatible successor to JPEG has long been in the works, and JPEG XL seems likely to eventually fill that role.&lt;/p&gt;
&lt;p&gt;Whereas the other new image formats mentioned above usually lose some quality when recompressing JPEG and other images that were already lossy-compressed, according to the &lt;a href=&#34;https://jpeg.org/jpegxl/&#34;&gt;Joint Photographic Experts Group (JPEG)&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Existing JPEG files can be losslessly transcoded to JPEG XL, significantly reducing their size.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;It has been reported that JPEG XL is expected to become available in its final standard form in 2022, and support is already available in preliminary form in some software (see the &lt;a href=&#34;https://en.wikipedia.org/wiki/JPEG_XL&#34;&gt;Wikipedia JPEG XL article&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;That of course means that for now, no major browsers &lt;a href=&#34;https://caniuse.com/jpegxl&#34;&gt;support JPEG XL natively&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;use-webp-now&#34;&gt;Use WebP now&lt;/h3&gt;
&lt;p&gt;So all the other new options are not yet usable for general web images. WebP is the current obvious choice, whether you want a lossy replacement for JPEG photos, a lossless replacement for PNG images, or a replacement for GIF animations.&lt;/p&gt;
&lt;p&gt;Our developers have set up automatic server-side app conversion of high-quality PNG originals to WebP or JPEG on the fly, with the image size dependent on the browser viewport size. And we have worked with Cloudinary, Cloudflare, and other CDNs to use their image conversion services. We are available to help with your projects too.&lt;/p&gt;
&lt;h3 id=&#34;reference&#34;&gt;Reference&lt;/h3&gt;
&lt;p&gt;The Mozilla Development Network (MDN) has excellent documentation of web image format details, filename suffixes, and support in the major browsers in its &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types&#34;&gt;Image file type and format guide&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Fun with Rational Numbers</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/02/fun-with-rational-numbers/"/>
      <id>https://www.endpointdev.com/blog/2022/02/fun-with-rational-numbers/</id>
      <published>2022-02-11T00:00:00+00:00</published>
      <author>
        <name>Darius Clynes</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/02/fun-with-rational-numbers/20210428_210604-sm.webp&#34; alt=&#34;Rock hillside with trees&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Zed Jensen --&gt;
&lt;p&gt;So, you say you adored learning long division in elementary school? Well here we are revisiting that wonderful activity we have missed so much: fabulous magical moments like dividing 1.0 by 7. The especially tedious exercise of dragging over one more of the endless 0’s and seeing how many 7’s would fit into that woefully large remainder. Soon the delight mounts as you desperately hope the next digit will be the last and you discover that beautiful 1 as a remainder.&lt;/p&gt;
&lt;p&gt;Undoubtedly you have wondered, like I have, what would happen if you divided 1.0 by 19? Would it repeat after only 8 digits? How many digits would it really take before it repeats?&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s exactly how I stumbled into the wonderful world of rational numbers. There I was, wondering and wandering along the gloomy Belgian beach — the tide way out as far as you could see — in the winter wind, and I couldn’t stop thinking how long it would take for that mantissa to repeat. I had actually already tried 1.0 over 19. Finally, on that miserable day, I got the courage to divide 1 by 23.&lt;/p&gt;
&lt;p&gt;Fortunately for me, I wasn’t alone, and I was able to enlist, coax, oblige, implore the help of baffled little Susan, my daughter who was 7 years old at the time. Her job would be to write out the digits in the sand as I discovered them, yelling them out to her, getting farther and farther away from her as the calculation got longer and longer. Finally, shrieking in the wind with delight as I hit 1 as the remainder, Susan seemed confused but amused.&lt;/p&gt;
&lt;p&gt;Well, it was so much fun I decided to share my excitement and enthusiasm while at the same time bringing attention to these often overlooked treasure chests of digits.&lt;/p&gt;
&lt;p&gt;After my satisfaction in coming up with the answer, the pleasure of which clearly eluded my daughter who was overjoyed that this exercise was over, I decided to write a computer program to discover how these numbers might turn out as the values became larger.&lt;/p&gt;
&lt;h3 id=&#34;numbers-as-music&#34;&gt;Numbers as music&lt;/h3&gt;
&lt;p&gt;As a composer, I thought it might be nice to portray the results in a sound representation. My first attempt was to use a fundamental sine wave and have each digit represent the number of overtones in the resulting sound. So the fundamental stayed constant, while the overtones quivered above not unlike Mongolian, Sardinian, or Tibetan Buddhist monk throat singing.&lt;/p&gt;
&lt;p&gt;The digit 0 became the lonely fundamental, the digit 1 became the first harmonic an octave above, 2 was an octave and a fifth, and so on, until 9 the 10th harmonic. For timing I added varying amounts of delay between the sound events proportional to the digits calculated.&lt;/p&gt;
&lt;p&gt;The first incarnation of my computer program was in 1999 in the programming language C, creating a sound wave file as an output. It created an interesting harmonic whistling kind of wind chime not unlike serrated plastic tubes that you swing around your head — the faster you swing, the higher the harmonics generated.&lt;/p&gt;
&lt;p&gt;Later, I thought of applying the repeating nature of the calculated series of numbers to create a periodic waveform itself, instead of using sine waves. Seems like an obvious path to explore, doesn&amp;rsquo;t it? I still have to try that sound experiment some day.&lt;/p&gt;
&lt;h3 id=&#34;visualizing-numbers&#34;&gt;Visualizing numbers&lt;/h3&gt;
&lt;p&gt;However, before continuing with sound, I did create a simple visual representation in JavaScript by making gradient-filled colored rectangles overlaid by arcs and line segments.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at my program showing results for some interesting numbers.&lt;/p&gt;
&lt;p&gt;For example, here is a screenshot of what the reciprocal of 7 (1 divided by 7) looks like in my little program:&lt;/p&gt;
&lt;h4 id=&#34;17&#34;&gt;1/7&lt;/h4&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/02/fun-with-rational-numbers/7.webp&#34; alt=&#34;Visual representation of 1 divided by 7&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;113&#34;&gt;1/13&lt;/h4&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/02/fun-with-rational-numbers/13.webp&#34; alt=&#34;Visual representation of 1 divided by 13&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;1523&#34;&gt;1/523&lt;/h4&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/02/fun-with-rational-numbers/523.webp&#34; alt=&#34;Visual representation of 1 divided by 523&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;a-demo&#34;&gt;A demo&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2022/02/fun-with-rational-numbers/demo/&#34; target=&#34;_blank&#34;&gt;Click here to try it out for yourself!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And make sure your sound is turned up a bit.&lt;/p&gt;
&lt;p&gt;Two types of sounds are generated: one, by ‘subtractive’ synthesis, using filtered white noise, and the second, by just using a sampled plucked string sound. The first one, whistle-like, starts with white noise bandpass filtered with the center frequency set at the notes of a piano and the bandwidth or Q of each filter set at one semitone. This produces airy, whispery, wind-like flute sounds.&lt;/p&gt;
&lt;h3 id=&#34;repeating-digits&#34;&gt;Repeating digits&lt;/h3&gt;
&lt;p&gt;By the way, it turns out that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1/19 produces 18 digits before repeating&lt;/li&gt;
&lt;li&gt;1/23 produces 22&lt;/li&gt;
&lt;li&gt;1/29 ⇒ 28&lt;/li&gt;
&lt;li&gt;1/263 ⇒ 262 repeating digits, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nice pattern, right? Seems like you can safely say that a reciprocal will never have a mantissa with more digits than the number itself.&lt;/p&gt;
&lt;h3 id=&#34;related-resources&#34;&gt;Related resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API#guides_and_tutorials&#34;&gt;Web Audio API&lt;/a&gt; components allow you to construct ‘circuits’ like you would with wires on an analog synthesizer, plugging outputs of one component into another, by ‘connecting’ modules.&lt;/li&gt;
&lt;li&gt;Olivia Jack&amp;rsquo;s work:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ojack.xyz/PIXELSYNTH/&#34;&gt;PIXELSYNTH&lt;/a&gt; is a very nice version of this concept.&lt;/li&gt;
&lt;li&gt;Her &lt;a href=&#34;https://hydra.ojack.xyz/&#34;&gt;main website&lt;/a&gt; has other very nice visual coding experiments.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Liquid Galaxy at 2017 BOMA International Annual Conference &amp; Expo</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2017/07/liquid-galaxy-at-2017-boma/"/>
      <id>https://www.endpointdev.com/blog/2017/07/liquid-galaxy-at-2017-boma/</id>
      <published>2017-07-03T00:00:00+00:00</published>
      <author>
        <name>Ben Witten</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;a href=&#34;/blog/2017/07/liquid-galaxy-at-2017-boma/image-0-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; data-original-height=&#34;792&#34; data-original-width=&#34;1364&#34; height=&#34;372&#34; src=&#34;/blog/2017/07/liquid-galaxy-at-2017-boma/image-0.png&#34; width=&#34;640&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We just returned from Nashville after bringing &lt;a href=&#34;https://www.visionport.com/&#34;&gt;Liquid Galaxy&lt;/a&gt; to the 2017 BOMA International Annual Conference &amp;amp; Expo. Commercial real estate has always been a seamless fit for Liquid Galaxy due to the system’s ability to showcase real estate and property data in an interactive and panoramic setting.  Within real estate, Liquid Galaxy was first used by CBRE and has since been adopted by Hilton, JLL, and Prologis to name a few.&lt;/p&gt;
&lt;p&gt;In preparation for BOMA (Building Owners and Managers Association), we prepared sample commercial real estate content on our content management system to be displayed on the Liquid Galaxy. This included the creation of content about Hudson Yards, the new development being built in lower Manhattan.&lt;/p&gt;
&lt;p&gt;The content that was created demonstrates how brokers and directors at commercial real estate companies can tell an immersive, panoramic, and interactive story to their potential clients and investors. Future developments can be shown in developing areas, and with the content management system you can create stories that include videos, images, and 3D models of these future developments. The following video demonstrates this well:&lt;/p&gt;
&lt;iframe allowfullscreen=&#34;&#34; frameborder=&#34;0&#34; height=&#34;360&#34; src=&#34;https://www.youtube.com/embed/OGlDv1NHD7A&#34; width=&#34;640&#34;&gt;&lt;/iframe&gt;
&lt;p&gt;We were able to effectively showcase our ability to incorporate 3D models and mapping layers at BOMA through the use of Google Earth, Cesium, ArcGIS, Unity, and Sketchfab. We were also able to pull data and develop content for neighboring booths and visitors, demonstrating what an easy and data-agnostic platform Liquid Galaxy can be.&lt;/p&gt;
&lt;p&gt;We’re very excited about the increasing traction we have in the real estate industry, and hope our involvement with BOMA will take that to the next level. If you’d like to learn more about Liquid Galaxy, please visit &lt;a href=&#34;https://www.visionport.com/&#34;&gt;our website&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Updating Firefox and the Black Screen</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/10/updating-firefox-and-black-screen/"/>
      <id>https://www.endpointdev.com/blog/2014/10/updating-firefox-and-black-screen/</id>
      <published>2014-10-16T00:00:00+00:00</published>
      <author>
        <name>Jeff Boes</name>
      </author>
      <content type="html">
        &lt;p&gt;If you are updating your &lt;a href=&#34;https://support.mozilla.org/en-US/kb/update-firefox-latest-version&#34;&gt;Firefox installation&lt;/a&gt; for Windows and you get a puzzling black screen of doom, here’s a handy tip: disable graphics acceleration.&lt;/p&gt;
&lt;p&gt;The symptoms here are that after you upgrade Firefox to version 33, the browser will launch into a black screen, possibly with a black dialog box (it’s asking if you want to choose Firefox to be your default browser). Close this as you won’t be able to do much with it.&lt;/p&gt;
&lt;p&gt;Launch Firefox by holding down the SHIFT key and clicking on the Firefox icon. It will ask if you want to reset Firefox (Nope!) or launch in Safe mode (Yes).&lt;/p&gt;
&lt;p&gt;Once you get to that point, click the “Open menu” icon (three horizonal bars, probably at the far right of your toolbar). Choose “Preferences”, “Advanced”, and uncheck “Use hardware acceleration when available”.&lt;/p&gt;
&lt;p&gt;Close Firefox, relaunch as normal, and you should be AOK. You can try re-enabling graphics acceleration if and when your graphics driver is updated.&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href=&#34;https://support.mozilla.org/questions/1025438&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Point Clouds on the Liquid Galaxy</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/07/point-clouds-on-liquid-galaxy/"/>
      <id>https://www.endpointdev.com/blog/2014/07/point-clouds-on-liquid-galaxy/</id>
      <published>2014-07-31T00:00:00+00:00</published>
      <author>
        <name>Josh Tolley</name>
      </author>
      <content type="html">
        &lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2014/07/point-clouds-on-liquid-galaxy/image-0.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;262&#34; src=&#34;/blog/2014/07/point-clouds-on-liquid-galaxy/image-0.jpeg&#34; width=&#34;400&#34;/&gt;&lt;/a&gt;&lt;p&gt;&lt;small&gt;Image by &lt;a href=&#34;http://commons.wikimedia.org/wiki/User:Stoermerjp&#34;&gt;Stoermerjp&lt;/a&gt;, unmodified &lt;a href=&#34;http://creativecommons.org/licenses/by-sa/3.0/&#34;&gt;(CC BY-SA 3.0)&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;The Liquid Galaxy began as a system to display geographic data through Google Earth, but it has expanded quickly as a display platform for other types of information. We’ve used Liquid Galaxies for panoramic images and video, three dimensional models of all sorts, as well as time-lapse renderings of weather, infrastructure, and economic data. Now we’ve added support for a new type of data, the &lt;a href=&#34;https://en.wikipedia.org/wiki/Point_cloud&#34;&gt;point cloud&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;“Point cloud” is simply the common term for a data set consisting of individual points, often in three-dimensional space, and frequently very large, containing thousands or millions of entries. Points in a cloud can include not just coordinate data, but other information as well, and because this sort of data can come from many different fields, the possible variations are endless. For instance, the terrain features visible in Google Earth began life as point clouds, the output of an aerial scanning device such as a LIDAR scanner. These scanners sweep their field of view rapidly, scanning millions of points to determine their location and any other interesting measurements—​color, for instance, or temperature—​and with that data create a point cloud. Smaller scale hardware scanners have made their way into modern life, scanning rooms and buildings, or complex objects. A few years ago, &lt;a href=&#34;https://techcrunch.com/2008/07/14/radiohead-partners-with-google-for-music-video-launch/&#34;&gt;the band Radiohead collaborated with Google&lt;/a&gt; to use the 3-D scanning techniques to film a music video, and published the resulting point cloud on &lt;a href=&#34;https://code.google.com/p/radiohead/&#34;&gt;Google Code&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center; float: right; clear: right&#34;&gt;&lt;p&gt;&lt;a href=&#34;/blog/2014/07/point-clouds-on-liquid-galaxy/image-1.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;320&#34; src=&#34;/blog/2014/07/point-clouds-on-liquid-galaxy/image-1.jpeg&#34; width=&#34;228&#34;/&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;small&gt;Image by &lt;a href=&#34;https://commons.wikimedia.org/wiki/User:Xorx&#34;&gt;Xorx&lt;/a&gt;, unmodified &lt;a href=&#34;https://creativecommons.org/licenses/by-sa/3.0/&#34;&gt;(CC BY-SA 3.0)&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;For the Liquid Galaxy platform, we modified an open source point cloud viewer called &lt;a href=&#34;http://potree.org/&#34;&gt;Potree&lt;/a&gt; to allow one master instance to control several others. Potree is a WebGL application. It runs in a browser, and depends on &lt;a href=&#34;https://threejs.org/&#34;&gt;three.js&lt;/a&gt;, a widely used WebGL library. Generally speaking, to convert an application to run on a Liquid Galaxy, the developer must give the software two separate modes: one which allows normal user control and transmits information to a central data bus, and another which receives information from the bus and uses it to create its own modified display. In this case, where the application simply loads a model and lets the user move it around, the “master” mode tracks all camera movements and sends them to the other Liquid Galaxy nodes, which will draw the same point cloud in the same orientation as the master, but with the camera pointing offset to the left or right a certain amount. We’ve dubbed our version &lt;a href=&#34;https://github.com/EndPointCorp/lg-potree&#34;&gt;lg-potree&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This marks the debut of a simple three.js extension we’ve been working on, which we’ve called &lt;a href=&#34;https://github.com/EndPointCorp/lg-three&#34;&gt;lg-three.js&lt;/a&gt;, designed to make it easy to adapt three.js applications for the Liquid Galaxy. lg-three.js gives the programmer an interface for capturing and serializing things like camera movements or other scene changes on the master node, and de-serializing and using that data on the other nodes, hopefully without having to modify the original application much. Some applications’ structure doesn’t lend itself well to lg-three, but potree proved fairly straightforward.&lt;/p&gt;
&lt;p&gt;With that introduction, please enjoy this demonstration.&lt;/p&gt;
&lt;iframe allowfullscreen=&#34;&#34; frameborder=&#34;0&#34; height=&#34;315&#34; src=&#34;//www.youtube.com/embed/GiWjUI97viQ&#34; width=&#34;560&#34;&gt;&lt;/iframe&gt;

      </content>
    </entry>
  
    <entry>
      <title>Creating a Symbol Web Font</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/07/creating-symbol-web-font/"/>
      <id>https://www.endpointdev.com/blog/2014/07/creating-symbol-web-font/</id>
      <published>2014-07-17T00:00:00+00:00</published>
      <author>
        <name>Zed Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;Creating a custom font that only includes a few characters can be very useful. I was looking for a good way to display left and right arrows for navigating between clients and also between team members on our site and after doing some research, creating a custom font seemed like a good way that would be small and that would support all kinds of screens and browsers. So, here I’ll show how to create a web font with a few custom characters in it that you can use on your website.&lt;/p&gt;
&lt;p&gt;You’ll need to get the free, open source vector graphics editor &lt;a href=&#34;https://inkscape.org/en/&#34;&gt;Inkscape&lt;/a&gt; and familiarize yourself with its drawing tools.&lt;/p&gt;
&lt;p&gt;To start, open Inkscape and open the SVG font editor by clicking Text -&amp;gt; SVG Font Editor. Under the font column, click &amp;ldquo;New&amp;rdquo; and then name your new font.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2014/07/creating-symbol-web-font/image-0-big.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/07/creating-symbol-web-font/image-0.jpeg&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;/div&gt;
&lt;p&gt;Now you can start adding characters. Begin by adding as many glyphs as you need and choosing letters for your character to be represented by. Only use characters that you can find on a standard QWERTY keyboard, as FontSquirrel (which we’ll use to convert this to a web font) won’t work with, for instance, Unicode special characters.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2014/07/creating-symbol-web-font/image-1-big.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/07/creating-symbol-web-font/image-1.jpeg&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Now, for each symbol, draw it using Inkscape’s tools and make sure that its dimensions are roughly 750 pixels high (which will be about the height of an uppercase letter) and that it’s flush with the bottom of the canvas.&lt;/p&gt;
&lt;p&gt;When your symbol looks like you want it to, make sure that all of the shapes you used to form it are selected and merge them together with Path -&amp;gt; Union. When you’re done, you should have a single object, your glyph. Now, select your glyph and do Path -&amp;gt; Object to Path.&lt;/p&gt;
&lt;p&gt;To add this symbol to your new font, select your object and the corresponding glyph and click “Get curves from selection.”&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2014/07/creating-symbol-web-font/image-2-big.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/07/creating-symbol-web-font/image-2.jpeg&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;To test, enter the character you’re using for your symbol in the “Preview Text” area. If it shows your symbol, you’re set. Otherwise, you need to make sure that you merged and converted your object to a path correctly.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2014/07/creating-symbol-web-font/image-3-big.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/07/creating-symbol-web-font/image-3.jpeg&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;After you’ve repeated these steps with every symbol you need, save with Inkscape as an SVG. We need to convert this to a TrueType font, so go to &lt;a href=&#34;http://www.freefontconverter.com/&#34;&gt;www.freefontconverter.com/&lt;/a&gt; (or any other font converter) and convert to .ttf.&lt;/p&gt;
&lt;p&gt;The last thing you need to do before using your font in your webpage is convert it to a webfont. Fortunately, &lt;a href=&#34;https://www.fontsquirrel.com/&#34;&gt;FontSquirrel&lt;/a&gt; makes this easy. Go to &lt;a href=&#34;https://www.fontsquirrel.com/tools/webfont-generator&#34;&gt;FontSquirrel’s webfont generator&lt;/a&gt; and upload your TrueType font. After the conversion has finished, you’ll get a zipfile with the font in several different webfont formats, and even an HTML page telling you how to use it in a webpage.&lt;/p&gt;
&lt;p&gt;Have fun creating custom webfonts!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Supporting Apple Retina displays on the Web</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/05/supporting-apple-retina-displays-on-web/"/>
      <id>https://www.endpointdev.com/blog/2014/05/supporting-apple-retina-displays-on-web/</id>
      <published>2014-05-27T00:00:00+00:00</published>
      <author>
        <name>Phineas Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;Apple’s &lt;a href=&#34;https://en.wikipedia.org/wiki/Retina_Display&#34;&gt;Retina displays&lt;/a&gt; (on Mac desktop &amp;amp; laptop computers, and on iPhones and iPads) have around twice the pixel density of traditional displays. Most recent Android phones and tablets have higher-resolution screens as well.&lt;/p&gt;
&lt;p&gt;I was recently given the task of adding support for these higher-resolution displays to our &lt;a href=&#34;/&#34;&gt;End Point company website&lt;/a&gt;. Our imagery had been created prior to Retina displays being commonly used, but even now many web developers still overlook supporting high-resolution screens because it hasn’t been part of the website workflow before, because they aren’t simple to cope with, and since most people don’t notice any lack of sharpness without comparing low &amp;amp; high-resolution images side by side.&lt;/p&gt;
&lt;p&gt;Most images which are not designed for Retina displays look blurry on them, like this:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2014/05/supporting-apple-retina-displays-on-web/image-0-big.png&#34; imageanchor=&#34;1&#34; style=&#34;display:inline&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;266&#34; src=&#34;/blog/2014/05/supporting-apple-retina-displays-on-web/image-0.png&#34; width=&#34;266&#34;/&gt;&lt;/a&gt;
&lt;a href=&#34;/blog/2014/05/supporting-apple-retina-displays-on-web/image-1-big.png&#34; imageanchor=&#34;1&#34; style=&#34;display:inline&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;266&#34; src=&#34;/blog/2014/05/supporting-apple-retina-displays-on-web/image-1.png&#34; width=&#34;266&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The higher-resolution image is on the left, and the lower-resolution image is on the right.&lt;/p&gt;
&lt;p&gt;Now, to solve this problem, you need to serve a larger, higher quality image to Retina displays. There are several different ways to do this. I’ll cover a few ways to do it, and explain how I implemented it for our site.&lt;/p&gt;
&lt;h3 id=&#34;retinajs&#34;&gt;Retina.js&lt;/h3&gt;
&lt;p&gt;As I was researching ways to implement support for Retina displays, I found that a popular suggestion is the JavaScript library &lt;a href=&#34;http://imulus.github.io/retinajs/&#34;&gt;Retina.js&lt;/a&gt;. Retina.js automatically detects Retina screens, and then for each image on the page, it checks the web server for a Retina image version under the same name with @2x before the suffix. For example, when fetching the image background.jpg on a Retina-capable system, it would afterward look for &lt;a href=&#34;mailto:background@2x.jpg&#34;&gt;background@2x.jpg&lt;/a&gt; and serve that if it’s available.&lt;/p&gt;
&lt;p&gt;Retina.js makes it relatively painless to deal with serving Retina images to the correct people, but it has a couple of large problems. First, it fetches and replaces the Retina image &lt;em&gt;after&lt;/em&gt; the default image, serving both the normal and Retina images to Retina users, greatly increasing download size and time.&lt;/p&gt;
&lt;p&gt;Second, Retina.js does not use the correct image if the browser window is moved from a Retina display to a non-Retina display or vice versa when using multiple monitors. For example, if an image is loaded on a standard 1080p monitor and then the browser is moved to a Retina display, it will show the incorrect, low-res image.&lt;/p&gt;
&lt;h3 id=&#34;using-css-for-background-images&#34;&gt;Using CSS for background images&lt;/h3&gt;
&lt;p&gt;Doesn’t the “modern web” have a way to handle this natively in HTML &amp;amp; CSS? For sites using CSS background images, CSS media queries will do the trick:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;media&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;only&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;screen&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;and&lt;/span&gt; (&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;-webkit-min-device-pixel-ratio&lt;/span&gt;: &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;2&lt;/span&gt;), (&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;min-resolution&lt;/span&gt;: &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;192dpi&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  .&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;icon&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;background-image&lt;/span&gt;: &lt;span style=&#34;color:#038&#34;&gt;url&lt;/span&gt;(&lt;span style=&#34;color:#2b2;background-color:#f0fff0&#34;&gt;icon@2x.png&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;background-size&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;20&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;px&lt;/span&gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;20&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;px&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But this method only works with CSS background images, so for our site and a lot of other sites, it will only be useful for a small number of images.&lt;/p&gt;
&lt;p&gt;Take a look at this &lt;a href=&#34;https://css-tricks.com/snippets/css/retina-display-media-query/&#34;&gt;CSS-Tricks&lt;/a&gt; page for some excellent examples of Retina (and other higher-res display) support.&lt;/p&gt;
&lt;h3 id=&#34;server-side-checks-for-retina-images&#34;&gt;Server-side checks for Retina images&lt;/h3&gt;
&lt;p&gt;A very efficient way to handle all types of images is to have the browser JavaScript set a cookie that tells the web server whether to serve Retina or standard images. That will keep data transfer to a minimum, with a minimum of trickery required in the browser. You’ll still need to create an extra Retina-resolution image for every standard image on the server. And you’ll need to have a dynamic web process run for every image served. The &lt;a href=&#34;https://web.archive.org/web/20141109113432/http://retina-images.complexcompulsions.com/&#34;&gt;Retina Images&lt;/a&gt; open source PHP program shows how to do this.&lt;/p&gt;
&lt;h3 id=&#34;why-we-didnt-use-these-methods&#34;&gt;Why we didn’t use these methods&lt;/h3&gt;
&lt;p&gt;There is one reason common to all of these methods which made us decide against them: All of them require you to maintain multiple versions of each image. This ends up taking a lot of time and effort. It also means your content distribution network (CDN) or other HTTP caches will have twice as many image files to load and cache, increasing cache misses and data transfer. It also uses more disk space, which isn’t a big problem for the small number of images on our website, but on an ecommerce website with many thousands of images, it adds up quickly.&lt;/p&gt;
&lt;p&gt;We would feel compelled to have the separate images if it were necessary if the Retina images were much larger and slow down the browsing experience for non-Retina users for no purpose. But instead we decided on the following solution that we saw others describe.&lt;/p&gt;
&lt;h3 id=&#34;serving-retina-images-to-everybody-how-we-did-it&#34;&gt;Serving Retina images to everybody (how we did it)&lt;/h3&gt;
&lt;p&gt;We read that you can serve Retina images to everyone, but we immediately thought that wouldn’t work out well. We were sure that the Retina images would be several times larger than the normal images, wasting a ton of bandwidth for anyone not using a Retina screen. We were very pleasantly surprised to find out that this wasn’t the case at all.&lt;/p&gt;
&lt;p&gt;After testing on a few images, I found I could get Retina images within 2-3 KB of the normal images while keeping the visual fidelity, by dropping the JPEG compression rate. How? Because the images were being displayed at a smaller size than they were, the compression artifacts weren’t nearly as noticeable.&lt;/p&gt;
&lt;p&gt;These are the total file sizes for each image on our &lt;a href=&#34;/team/&#34;&gt;team page&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Retina  Normal  Filename
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 10K    9.3K    adam_spangenthal.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 13K     13K    adam_vollrath.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 12K     11K    benjamin_goldstein.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;7.6K    4.2K    bianca_rodrigues.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 14K     13K    brian_buchalter.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 13K     15K    brian_gadoury.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;7.5K    8.0K    brian_zenone.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;9.8K    6.6K    bryan_berry.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 12K     11K    carl_bailey.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;6.9K     15K    dave_jenkins.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 13K     13K    david_christensen.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;7.7K     21K    emanuele_calo.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 16K     16K    erika_hamby.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 13K     11K    gerard_drazba.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 14K     14K    greg_davidson.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 14K     12K    greg_sabino_mullane.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 14K     15K    jeff_boes.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 14K     12K    jon_jensen.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 13K     12K    josh_ausborne.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 13K     14K    josh_tolley.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 13K     11K    josh_williams.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;8.9K    9.5K    kamil_ciemniewski.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 13K     21K    kent_krenrich.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 15K     12K    kiel_christofferson.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;9.9K     11K    kirk_harr.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;7.7K     13K    marco_manchego.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 12K     13K    marina_lohova.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 14K     11K    mark_johnson.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;7.3K     13K    matt_galvin.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 15K     12K    matt_vollrath.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;6.6K     14K    miguel_alatorre.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 13K     14K    mike_farmer.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;7.1K     19K    neil_elliott.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;9.9K    9.0K    patrick_lewis.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 13K    5.6K    phin_jensen.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 12K     14K    richard_templet.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 12K    9.9K    rick_peltzman.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 14K     13K    ron_phipps.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;9.7K     14K    selvakumar_arumugam.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;9.3K     15K    spencer_christensen.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 12K     12K    steph_skardal.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 15K     18K    steve_yoman.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;6.7K     15K    szymon_guz.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;7.5K    6.8K    tim_case.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 15K     21K    tim_christofferson.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;9.3K     12K    will_plaut.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 12K     14K    wojciech_ziniewicz.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 12K    9.9K    zed_jensen.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TOTALS
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Retina: 549.4K
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Normal: 608.8K&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is where I found the biggest, and best, surprise. The cumulative size of the Retina image files was &lt;em&gt;less&lt;/em&gt; than that of the original images. So now we have support for Retina displays, making our website look nice on modern screens, while actually using less data transfer. We don’t need JavaScript, cookies, or any extra server-side trickery to do this. And best of all, we don’t have to maintain a separate set of Retina images.&lt;/p&gt;
&lt;p&gt;Once you’ve seen the difference in quality on a Retina screen or a new Android phone, you’ll wonder how you ever were able to tolerate the lower-resolution images. And at least for our selection of JPEG images, there’s not even a file size penalty to pay!&lt;/p&gt;
&lt;h3 id=&#34;reference-reading&#34;&gt;Reference reading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ivomynttinen.com/blog/a-guide-for-creating-a-better-retina-web/&#34;&gt;A guide for creating a better retina web&lt;/a&gt; by Ivo Mynttinen&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.leemunroe.com/designing-for-high-resolution-retina-displays/&#34;&gt;5 Things I Learned Designing For High-Resolution Retina Displays&lt;/a&gt; by Lee Munroe&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/library/safari/documentation/NetworkingInternet/Conceptual/SafariImageDeliveryBestPractices/Introduction/Introduction.html&#34;&gt;About Proper Image Delivery on the Web&lt;/a&gt; on the Safari Developer Library&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/library/safari/documentation/NetworkingInternet/Conceptual/SafariImageDeliveryBestPractices/ServingImagestoRetinaDisplays/ServingImagestoRetinaDisplays.html&#34;&gt;Serving Images Efficiently to Displays of Varying Pixel Density&lt;/a&gt; on the Safari Developer Library&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>WebP images experiment on End Point website</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/01/webp-images-experiment-on-end-point/"/>
      <id>https://www.endpointdev.com/blog/2014/01/webp-images-experiment-on-end-point/</id>
      <published>2014-01-28T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;WebP is an image format for RGB images on the web that supports both lossless (like PNG) and lossy (like JPEG) compression. It was released by Google in September 2010 with open source reference software available under the BSD license, accompanied by a royalty-free public patent license, making it clear that they want it to be widely adopted by any and all without any encumbrances.&lt;/p&gt;
&lt;p&gt;Its main attraction is smaller file size at similar quality level. It also supports an alpha channel (transparency) and animation for both lossless and lossy images. Thus it is the first image format that offers the transparency of PNG in lossy images at much smaller file size, and animation only available in the archaic limited-color GIF format.&lt;/p&gt;
&lt;h3 id=&#34;comparing-quality--size&#34;&gt;Comparing quality &amp;amp; size&lt;/h3&gt;
&lt;p&gt;While considering WebP for an experiment on our own website, we were very impressed by its file size to quality ratio. In our tests it was even better than generally claimed. Here are a few side-by-side examples from our site. You’ll only see the WebP version if your browser supports it:&lt;/p&gt;
&lt;table cellspacing=&#34;10&#34;&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;img alt=&#34;&#34; height=&#34;133&#34; src=&#34;/blog/2014/01/webp-images-experiment-on-end-point/marina_lohova.jpg&#34; width=&#34;133&#34;/&gt;&lt;br/&gt;13,420 bytes JPEG&lt;/td&gt;
&lt;td&gt;&lt;img alt=&#34;&#34; height=&#34;133&#34; src=&#34;/blog/2014/01/webp-images-experiment-on-end-point/marina_lohova.webp&#34; width=&#34;133&#34;/&gt;&lt;br/&gt;2776 bytes WebP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img alt=&#34;&#34; height=&#34;133&#34; src=&#34;/blog/2014/01/webp-images-experiment-on-end-point/josh_williams.jpg&#34; width=&#34;133&#34;/&gt;&lt;br/&gt;14,734 bytes JPEG&lt;/td&gt;
&lt;td&gt;&lt;img alt=&#34;&#34; height=&#34;133&#34; src=&#34;/blog/2014/01/webp-images-experiment-on-end-point/josh_williams.webp&#34; width=&#34;133&#34;/&gt;&lt;br/&gt;3386 bytes WebP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The original PNG images were converted by ImageMagick to JPEG, and by &lt;code&gt;cwebp -q 80&lt;/code&gt; to WebP. I think we probably should increase the WebP quality a bit to keep a little of the facial detail that flattens out, but it’s amazing how good these images look for file sizes that are only 17% and 23% of the JPEG equivalent.&lt;/p&gt;
&lt;p&gt;One of our website’s background patterns has transparency, making the PNG format a necessity, but it also has a gradient, which PNG compression is particularly inefficient with. WebP is a major improvement there, at 13% the size of the PNG. The image is large so I won’t show it here, but you can follow the links if you’d like to see it:&lt;/p&gt;
&lt;table cellspacing=&#34;15&#34;&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&#34;right&#34;&gt;337,186 bytes&lt;/td&gt;&lt;td&gt;&lt;a href=&#34;/blog/2014/01/webp-images-experiment-on-end-point/container-pattern.png&#34;&gt;container-pattern.png&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;right&#34;&gt;43,270 bytes&lt;/td&gt;&lt;td&gt;&lt;a href=&#34;/blog/2014/01/webp-images-experiment-on-end-point/container-pattern.webp&#34;&gt;container-pattern.webp&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&#34;browser-support&#34;&gt;Browser support&lt;/h3&gt;
&lt;p&gt;So, what is the downside? WebP is currently natively supported only in Chrome and Opera among the major browsers, though amazingly, support for other browsers can be added via WebPJS, a JavaScript WebP renderer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update! As of 2021, all current major browsers support WebP image rendering.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Why don’t the other browsers add support given the liberal license? Especially Firefox you’d expect to support it. In fact a patch has been pending for years, and a debate about adding support still smolders. Why?&lt;/p&gt;
&lt;p&gt;WebP does not yet support progressive rendering, Exif tagging, non-RGB color spaces such as CMYK, and is limited to 16,384 pixels per side. Some Firefox developers feel that it would do the Internet community a disservice to support an image format still under development and cause uncertain levels of support in various clients, so they will not accept WebP in its current state.&lt;/p&gt;
&lt;p&gt;Many batch image-processing tools now support WebP, and there is a free Photoshop plug-in for it. Some websites are quietly using it just because of the cost savings due to reduced bandwidth.&lt;/p&gt;
&lt;p&gt;For our first experiment serving WebP images from the End Point website, I decided to serve WebP images only to browsers that claim to be able to support it. They advertise that support in this HTTP request header:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Accept: image/webp,*/*;q=0.8&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That says explicitly that the browser can render image/webp, so we just need to configure the server to send WebP images. One way to do that is in the application server, by having it send URLs pointing to WebP files.&lt;/p&gt;
&lt;p&gt;Let’s plan to have both common format (JPEG or PNG) and WebP files side by side, and then try a way that is transparent to the application and can be enabled or disabled very easily.&lt;/p&gt;
&lt;h3 id=&#34;web-server-rewrites&#34;&gt;Web server rewrites&lt;/h3&gt;
&lt;p&gt;It’s possible to set up the web server to transparently serve WebP instead of JPEG or PNG if a matching file exists. Based on some examples other people posted, we used this nginx configuration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;set $webp &amp;#34;&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;set $img &amp;#34;&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;if ($http_accept ~* &amp;#34;image/webp&amp;#34;) { set $webp &amp;#34;can&amp;#34;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;if ($request_filename ~* &amp;#34;(.*)\.(jpe?g|png)$&amp;#34;) { set $img $1.webp; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;if (-f $img) { set $webp &amp;#34;$webp-have&amp;#34;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;if ($webp = &amp;#34;can-have&amp;#34;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    add_header Vary Accept;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rewrite &amp;#34;(.*)\.\w+$&amp;#34; $1.webp break;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    break;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It’s also good to add to /etc/nginx/mime.types:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;image/webp .webp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;so that .webp files are served with the correct MIME type instead of the default application/octet-stream, or worse, text/plain with perhaps a bogus character set encoding.&lt;/p&gt;
&lt;p&gt;Then we just make sure identically-named .webp files match .png or .jpg files, such as those for our examples above:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-rw-rw-r-- 337186 Nov  6 14:10 container-pattern.png
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-rw-rw-r--  43270 Jan 28 08:14 container-pattern.webp
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-rw-rw-r--  14734 Nov  6 14:10 josh_williams.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-rw-rw-r--   3386 Jan 28 08:14 josh_williams.webp
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-rw-rw-r--  13420 Nov  6 14:10 marina_lohova.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-rw-rw-r--   2776 Jan 28 08:14 marina_lohova.webp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A request for a given $file.png will work as normal in browsers that don’t advertise WebP support, while those that do will instead receive the $file.webp image.&lt;/p&gt;
&lt;p&gt;The image is still being requested with a name ending in .jpg or .png, but that’s just a name as far as both browser and server are concerned, and the image type is determined by the MIME type in the HTTP response headers (and/or by looking at the file’s magic numbers). So the browser will have a file called $something.jpg in the DOM and in its cache, but it will actually be a WebP file. That’s ok, but could be confusing to users who save the file for whatever reason and find it isn’t actually the JPEG they were expecting.&lt;/p&gt;
&lt;h3 id=&#34;301302-redirect-option&#34;&gt;301/302 redirect option&lt;/h3&gt;
&lt;p&gt;One remedy for that is to serve the WebP file via a 301 or 302 redirect instead of transparently in the response, so that the browser knows it’s dealing with a different file named $something.webp. To do that we changed the nginx configuration like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rewrite &amp;#34;(.*)\.\w+$&amp;#34; $1.webp permanent;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That adds a little bit of overhead, around 100-200 bytes unless large cookies are sent in the request headers, and another network round-trip or two, though it’s still a win with the reduced file sizes we saw. However, I found that it isn’t even necessary right now due to an interesting behavior in Chrome that may even be intentional to cope with this very situation. (Or it may be a happy accident.)&lt;/p&gt;
&lt;h3 id=&#34;chrome-image-download-behavior&#34;&gt;Chrome image download behavior&lt;/h3&gt;
&lt;p&gt;Versions of Chrome I tested only send the Accept: image/webp [etc.] request header when fetching images from an HTML page, not when you manually request a single file or asking the browser to save the image from the page by right-clicking or similar. In those cases the Accept header is not sent, so the server doesn’t know the browser supports WebP, so you get the JPEG or PNG you asked for. That was actually a little confusing to hunt down by sniffing the HTTP traffic on the wire, but it may be a nice thing for users as long as WebP is still less-known.&lt;/p&gt;
&lt;h3 id=&#34;batch-conversion&#34;&gt;Batch conversion&lt;/h3&gt;
&lt;p&gt;It’s fun to experiment, but we needed to actually get all the images converted for our website. Surprisingly, even converting from JPEG isn’t too bad, though you need a higher quality setting and the file size will be larger. Still, for best image quality at the smallest file size, we wanted to start with original PNG images, not recompress JPEGs.&lt;/p&gt;
&lt;p&gt;To make that easy, we wrote &lt;a href=&#34;https://gist.github.com/jonjensen/8677031&#34;&gt;two shell scripts&lt;/a&gt; for Linux, bash, and cwebp. We found a few exceptional images that were larger in WebP than in PNG or JPEG, so the script deletes any WebP file that is not smaller, and our nginx configuration will in that case not find a .webp file and will serve the original PNG or JPEG.&lt;/p&gt;
&lt;h3 id=&#34;full-page-download-sizes-compared&#34;&gt;Full-page download sizes compared&lt;/h3&gt;
&lt;p&gt;Here are performance tests run by WebPageTest.org using Chrome 32 on Windows 7 on a simulated cable Internet connection. The total download size difference is most impressive, and on a slower mobile network or with higher latency (greater distance from the server) would affect the download time more.&lt;/p&gt;
&lt;table id=&#34;pagespeeds&#34;&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th align=&#34;center&#34; rowspan=&#34;2&#34;&gt;Page URL&lt;/th&gt;
&lt;th align=&#34;center&#34; colspan=&#34;2&#34;&gt;With WebP&lt;/th&gt;
&lt;th align=&#34;center&#34; colspan=&#34;2&#34;&gt;Without WebP&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th align=&#34;center&#34;&gt;Bytes&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;Time&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;Bytes&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;https://www.endpointdev.com/&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;374 KB&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;2.9s&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;850 KB&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;3.4s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;https://www.endpointdev.com/team/&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;613 KB&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;3.6s&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;1308 KB&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;4.1s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This article is not even close to a comprehensive shootout between WebP and other image types. There are other sites that consider the image format technical details more closely and have well-chosen sample images.&lt;/p&gt;
&lt;p&gt;My purpose here was to convert a real website in bulk to WebP without hand-tuning individual images or spending too much time on the project overall, and to see if the overall infrastructure is easy enough to set up, and the download size and speed improved enough to make it worth the trouble, and get real-world experience with it to see if we can recommend it for our clients, and in which situations.&lt;/p&gt;
&lt;p&gt;So far it seems worth it, and we plan to continue using WebP on our website. With empty browser caches, visit &lt;a href=&#34;/&#34;&gt;www.endpointdev.com&lt;/a&gt; using Chrome and then one of the browsers that doesn’t support WebP, and see if you notice a speed difference on first load, or any visual difference.&lt;/p&gt;
&lt;p&gt;I hope to see WebP further developed and more widely supported.&lt;/p&gt;
&lt;h3 id=&#34;further-reading&#34;&gt;Further reading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://developers.google.com/speed/webp/&#34;&gt;WebP project home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://en.wikipedia.org/wiki/WebP&#34;&gt;Wikipedia on WebP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Firefox debate: &lt;a href=&#34;https://bugzilla.mozilla.org/show_bug.cgi?id=600919&#34;&gt;bug #600919&lt;/a&gt;, &lt;a href=&#34;https://bugzilla.mozilla.org/show_bug.cgi?id=856375&#34;&gt;bug #856375&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://webpjs.appspot.com/&#34;&gt;WebPJS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Kamelopard update—​Panoramic camera simulation, and splines have returned</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2013/07/kamelopard-update-panoramic-camera/"/>
      <id>https://www.endpointdev.com/blog/2013/07/kamelopard-update-panoramic-camera/</id>
      <published>2013-07-16T00:00:00+00:00</published>
      <author>
        <name>Josh Tolley</name>
      </author>
      <content type="html">
        &lt;p&gt;A few days ago I pushed Kamelopard version 0.0.12 to &lt;a href=&#34;https://rubygems.org/gems/kamelopard&#34;&gt;RubyGems&lt;/a&gt;. This version includes a couple big items. The first of these is a new implementation of the spline code that was removed a while ago. As I mentioned in &lt;a href=&#34;/blog/2013/04/creating-smooth-flight-paths-in-google/&#34;&gt;a previous blog post&lt;/a&gt;, this original implementation was built in anticipation of an API that never materialized. The new version is built on the same API discussed in the previous post I mentioned, modified to support multidimensional functions. More information about these splines is available on the &lt;a href=&#34;https://github.com/liquidgalaxy/liquid-galaxy/wiki&#34;&gt;wiki for Liquid Galaxy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The other big new feature is the ability to simulate multiple-camera panoramic images, which we’re calling “multicam”. It has &lt;a href=&#34;https://github.com/LiquidGalaxy/liquid-galaxy/wiki/KamelopardMulticam&#34;&gt;its own wiki page&lt;/a&gt; as well, but I wanted to describe it in greater detail, because there’s a fair bit of 3D geometry involved that seemed blog-worthy. First, though, it’s important to understand the goal. In a Liquid Galaxy, each instance of Google Earth displays the view from one virtual “camera”. One display’s camera points exactly where you tell it to point; the others point to one side or the other, based on &lt;a href=&#34;https://github.com/LiquidGalaxy/liquid-galaxy/wiki/QuickStart&#34;&gt;a few settings&lt;/a&gt; in a Google Earth configuration file. When placed side-by-side in the right order, these displays form a single panoramic image. Google Earth itself figures out where these displays’ cameras should point, but for some applications, we wanted to be able to calculate those display angles and position the cameras on our own. For instance, it would sometimes be nice to pre-record a particular tour and play it back on a Liquid Galaxy as a simple video. For a seven-screen galaxy, we’ll need seven different video files, each with the same movements to and from the same geographic locations, but each with a slightly different camera orientation.&lt;/p&gt;
&lt;p&gt;Camera orientation is controlled by &lt;a href=&#34;https://developers.google.com/kml/documentation/kmlreference#abstractview&#34;&gt;KML AbstractView elements&lt;/a&gt;, of which there are two varieties: &lt;a href=&#34;https://developers.google.com/kml/documentation/kmlreference#camera&#34;&gt;Camera&lt;/a&gt;, and &lt;a href=&#34;https://developers.google.com/kml/documentation/kmlreference#lookat&#34;&gt;LookAt&lt;/a&gt;. The former tells the camera its position and orientation exactly; the latter describes it in terms of another point. For now, multicam only supports Camera objects, because LookAt is somewhat more complicated.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2013/07/kamelopard-update-panoramic-camera/image-0-big.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/07/kamelopard-update-panoramic-camera/image-0.png&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;A Camera object gives the camera’s latitude, longitude, and altitude, and three angles called heading, tilt, and roll. The camera initially points straight down, positioned so that north is up. When orienting the camera, Google Earth first rotates around the Z axis, which extends through the lens of the camera, by the heading amount. Then it rotates around the X axis, which extends through the sides of the camera, by the amount given by the tilt angle. Finally, it rotates around the Z axis again, by the roll amount. The Y axis always points in whatever direction the camera thinks is “up”.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2013/07/kamelopard-update-panoramic-camera/image-1-big.png&#34; imageanchor=&#34;1&#34; style=&#34;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/07/kamelopard-update-panoramic-camera/image-1.png&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;With that background, we’re ready to tackle the problem at hand. We’ll consider our cameras as shown in the drawing. Camera 0 is the master view, positively numbered cameras go off to the master camera’s right, and negatively numbered cameras to the left. Each camera has the same view angle. We want an API that will accept the master camera’s orientation, the number of the camera we’re interested in, and the view angle of each camera, and return the orientation for the camera number we gave. We’ll consider each camera’s orientation in terms of two vectors; the first will tell us what direction the camera is pointing, and the second, the up direction. I’ll call these the “camera” vector and the “up” vector. The first step will be to find these two vectors for the camera we want, and the second will be to translate that back into a Camera object.&lt;/p&gt;
&lt;p&gt;Getting the position of these vectors for any given camera is a simple application of &lt;a href=&#34;https://en.wikipedia.org/wiki/Rotation_matrix&#34;&gt;rotation matrices&lt;/a&gt;. In the Kamelopard code, there’s a function defined for rotating around each of the three axes. Here’s the example for the X axis; the others are all similar:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;rot_x&lt;/span&gt;(a)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#888&#34;&gt;# Convert the angle to radians&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   a = a * &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Math&lt;/span&gt;::&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;PI&lt;/span&gt; / &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;180&lt;/span&gt;.&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Matrix&lt;/span&gt;[[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;], [&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Math&lt;/span&gt;.cos(a), -&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt; * &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Math&lt;/span&gt;.sin(a)], [&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Math&lt;/span&gt;.sin(a), &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Math&lt;/span&gt;.cos(a)]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So we start with a camera pointed at the ground, and we multiply it by a rotation matrix, so that it points in the direction our camera would, before the heading, tilt, and roll rotations are applied. When we perform those rotations, we’ll end up with a vector pointed in the right direction for our camera. This code does just that.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# The camera vector is [0,0,1] rotated around the Y axis the amount&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# of the camera angle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;camera = rot_y(cam_angle) * &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Vector&lt;/span&gt;[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# The up vector is the same for all cameras&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;up = &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Vector&lt;/span&gt;[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;matrix = rot_z(heading) * rot_x(tilt) * rot_z(roll)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(h, t, r) = vector_to_camera(matrix * camera, matrix * up)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The last line of this snippet calculates the camera and up vectors, and passes them to the vector_to_camera() function, which takes care of the second step in the process: converting these vectors back into a usable heading, tilt, and roll. Two operations fundamental to linear algebra will become important here. Both take two vectors as input. The &lt;a href=&#34;https://en.wikipedia.org/wiki/Dot_product&#34;&gt;dot product&lt;/a&gt; returns product of the two vectors’ magnitudes and the cosine of the angle between them. We’ll use it here to find the angle between two vectors. The &lt;a href=&#34;https://en.wikipedia.org/wiki/Cross_product&#34;&gt;cross product&lt;/a&gt; returns a &lt;a href=&#34;https://en.wikipedia.org/wiki/Surface_normal&#34;&gt;normal&lt;/a&gt; vector, or a vector which is perpendicular to the two input vectors.&lt;/p&gt;
&lt;p&gt;First we want to calculate the heading, which we can find by calculating the angle between two planes. The first plane is defined by the camera vector and the Z axis, and the second is the plane of the Y and Z axes. To find the angle between two planes, we find the angle between their normal vectors. The normal vector of the YZ plane is simply the X axis; the normal vector for hte first plane is the cross product of the camera vector and the Z axis. The dot product lets us find the angle between these two vectors, which is our heading.&lt;/p&gt;
&lt;p&gt;Tilt is simply the angle between the camera vector and the original Z axis, but roll is a bit harder. To find it, we transform the original Y axis—​the original “up” vector—​using the heading and tilt calculated previously. We then find the angle between it and the current “up” vector, again using the dot product.&lt;/p&gt;
&lt;p&gt;These calculations underlie a simple API, which simply takes a view, for the original camera, the number of the camera we’re interested in getting, and the camera angle or total number of cameras. Like this, for instance:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;view_hash = {&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:latitude&lt;/span&gt; =&amp;gt; otp[&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:latitude&lt;/span&gt;], &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:longitude&lt;/span&gt; =&amp;gt; lo,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:altitudeMode&lt;/span&gt; =&amp;gt; otp[&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:altitudeMode&lt;/span&gt;], &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:altitude&lt;/span&gt; =&amp;gt; otp[&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:altitude&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:roll&lt;/span&gt; =&amp;gt; otp[&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:roll&lt;/span&gt;], &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:timestamp&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Kamelopard&lt;/span&gt;::&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;TimeStamp&lt;/span&gt;.new(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;1921-07-29&amp;#39;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:heading&lt;/span&gt; =&amp;gt; otp[&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:heading&lt;/span&gt;], &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:tilt&lt;/span&gt; =&amp;gt; otp[&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:tilt&lt;/span&gt;]}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;v = &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Kamelopard&lt;/span&gt;::&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Multicam&lt;/span&gt;.get_camera_view(make_view_from(view_hash), camera, &lt;span style=&#34;color:#080&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;CamCount&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


      </content>
    </entry>
  
    <entry>
      <title>Creating custom button graphics in Android</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2013/06/creating-custom-button-graphics-in/"/>
      <id>https://www.endpointdev.com/blog/2013/06/creating-custom-button-graphics-in/</id>
      <published>2013-06-07T00:00:00+00:00</published>
      <author>
        <name>Zed Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;In the Android timesheet app I’m working on, I have a scrollable layout of RadioButtons for the user to pick how much time they’ve spent on a project (see &lt;a href=&#34;/blog/2013/05/dynamically-adding-custom-radio-buttons/&#34;&gt;my earlier blog post about it&lt;/a&gt;), and for that I use custom button graphics to make it look nice. So, I’m going to show you how to do that with 9-patch PNGs and selector XML.&lt;/p&gt;
&lt;p&gt;First, what’s a 9-patch PNG? A 9-patch is a special PNG image where you specify regions that can be stretched to make room for text. Android will automatically resize a 9-patch to best fit whatever contents you give it. The tool you need to create a 9-patch image is included in the Android SDK Tools, so download that if you haven’t already.&lt;/p&gt;
&lt;p&gt;More information about 9-patch images can be found &lt;a href=&#34;https://developer.android.com/guide/topics/graphics/drawables#nine-patch&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Okay! I’ve got custom button graphics (72x72 for HDPI screens), drawn in the Gimp and saved in my project’s res/drawable-hdpi/ folder as button_selected.png and button_unselected.png:&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;
&lt;a href=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-0-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-0.png&#34;/&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-1-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-1.png&#34;/&gt;&lt;/a&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;To convert it to a 9-patch, browse to the tools/ directory of the Android SDK and run draw9patch. This will open a window with a graphical editor on the left, and a button preview on the right. The editor window is for specifying which parts of the image will be stretched. The top and left edges show this, and the right and bottom edges show which parts of the image can contain the text you put in the button.&lt;/p&gt;
&lt;p&gt;When you’ve finished with draw9patch, save the images in the same place, but with .9.png as the file extension (in this case, res/drawable-hdpi/button_selected.png will be res/drawable-hdpi/button_selected.9.png). Make sure to delete the old images, because Android R (the generated resource class) doesn’t use file extensions, so it can’t tell the difference between our two image types.&lt;/p&gt;
&lt;p&gt;Now, let’s try making a button with our custom graphics. Add a Button to your Activity XML, like so:&lt;/p&gt;
&lt;p&gt;res/layout/activity_main.xml&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;RelativeLayout xmlns:android=&amp;#34;http://schemas.android.com/apk/res/android&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   xmlns:tools=&amp;#34;http://schemas.android.com/tools&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:layout_width=&amp;#34;match_parent&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:layout_height=&amp;#34;match_parent&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:paddingBottom=&amp;#34;@dimen/activity_vertical_margin&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:paddingLeft=&amp;#34;@dimen/activity_horizontal_margin&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:paddingRight=&amp;#34;@dimen/activity_horizontal_margin&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:paddingTop=&amp;#34;@dimen/activity_vertical_margin&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   tools:context=&amp;#34;.MainActivity&amp;#34; &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;Button
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:id=&amp;#34;@+id/button1&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:layout_width=&amp;#34;wrap_content&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:layout_height=&amp;#34;wrap_content&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:background=&amp;#34;@drawable/button_selected&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:onClick=&amp;#34;doStuff&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:text=&amp;#34;@string/hello_world&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;/RelativeLayout&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, when we run it, it looks like this:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-2-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-2.png&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So we have our custom background working, but it’s the same (red) whether or not you’re pushing it. To use different images for different states, we can use selector XML. With ours, we just have two images, so it’s simple:&lt;/p&gt;
&lt;p&gt;res/drawable/button_selector.xml&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34;?&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;selector xmlns:android=&amp;#34;http://schemas.android.com/apk/res/android&amp;#34; &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;!-- When selected, use this image --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;item android:drawable=&amp;#34;@drawable/button_selected&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:state_pressed=&amp;#34;true&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;!-- When not selected, use this image --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;item android:drawable=&amp;#34;@drawable/button_unselected&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:state_pressed=&amp;#34;false&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;/selector&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, instead of pointing our Button at an image directly, we can reference the XML instead:&lt;/p&gt;
&lt;p&gt;res/layout/activity_main.xml&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;font color=&amp;#34;#969696&amp;#34;&amp;gt;android:layout_height=&amp;#34;wrap_content&amp;#34;&amp;lt;/font&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; android:background=&amp;#34;@drawable/button_selector&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;font color=&amp;#34;#969696&amp;#34;&amp;gt;android:onClick=&amp;#34;doStuff&amp;#34;&amp;lt;/font&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And when we run it, it looks great (assuming you like bright red)!&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;
&lt;a href=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-3-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-3.png&#34;/&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-4-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-4.png&#34;/&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;This project is on &lt;a href=&#34;https://github.com/obnoxiousorc/com.example.custombuttonsdemo&#34;&gt;GitHub,&lt;/a&gt; if you’d like to download it and try some stuff.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>CSS Conf 2013 — When Bootstrap Attacks!</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2013/06/css-conf-2013-when-bootstrap-attacks/"/>
      <id>https://www.endpointdev.com/blog/2013/06/css-conf-2013-when-bootstrap-attacks/</id>
      <published>2013-06-03T00:00:00+00:00</published>
      <author>
        <name>Greg Davidson</name>
      </author>
      <content type="html">
        &lt;img alt=&#34;Cssconf 2013&#34; border=&#34;0&#34; height=&#34;234&#34; src=&#34;/blog/2013/06/css-conf-2013-when-bootstrap-attacks/image-0.png&#34; style=&#34;display:block; margin-left:auto; margin-right:auto;&#34; title=&#34;cssconf-2013.png&#34; width=&#34;373&#34;/&gt;
&lt;p&gt;I attended the inaugural &lt;a href=&#34;https://web.archive.org/web/20130629170825/http://cssconf.com/speakers.html&#34;&gt;CSS Conf&lt;/a&gt; last week at Amelia Island, Florida. The conference was organized by &lt;a href=&#34;http://www.stubbornella.org/&#34;&gt;Nicole Sullivan&lt;/a&gt;, &lt;a href=&#34;http://brett.stimmerman.com/&#34;&gt;Brett Stimmerman&lt;/a&gt;, &lt;a href=&#34;https://snook.ca/&#34;&gt;Jonathan Snook&lt;/a&gt;, and &lt;a href=&#34;https://www.paulirish.com/&#34;&gt;Paul Irish&lt;/a&gt; and put on with help from a host of volunteers. The talks were presented in a single track style on a wide range of CSS-related topics; there was something interesting for everyone working in this space. I really enjoyed the conference, learned lots and had great discussions with a variety of people hacking on interesting things with CSS. In the coming days I will be blogging about some of the talks I attended and sharing what I learned, so stay tuned!&lt;/p&gt;
&lt;h3 id=&#34;when-bootstrap-attacks&#34;&gt;When Bootstrap Attacks&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;http://www.pamelafox.org/&#34;&gt;Pamela Fox&lt;/a&gt; had the opening slot and spoke about the experiences and challenges she faced when upgrading &lt;a href=&#34;https://getbootstrap.com/2.3.2/&#34;&gt;Bootstrap&lt;/a&gt; to V2 in a large web app (&lt;a href=&#34;https://www.coursera.org/&#34;&gt;Coursera&lt;/a&gt;). What she initially thought would be a quick project turned into a month-long “BOOTSTRAPV2ATHON”. Bootstrap styles were used throughout the project in dozens of PHP, CSS and JavaScript files. The fact that Bootstrap uses generic CSS class names like “alert”, “btn”, error etc made it very difficult to grep through the codebase for them. The Bootstrap classes were also used as hooks by the project’s JavaScript application code.&lt;/p&gt;
&lt;h3 id=&#34;lessons-learned&#34;&gt;Lessons Learned&lt;/h3&gt;
&lt;p&gt;Fox offered some tips for developers facing a similar situation. The first of which was to prefix the Bootstrap CSS classes (e.g. .tbs-alert) in order to decouple Bootstrap from the customizations in your project. Some requests have been made to the Bootstrap team on this front but the issue has not been addressed yet. In the meantime, devs can add a task to their build step (e.g. &lt;a href=&#34;https://gruntjs.com/&#34;&gt;Grunt&lt;/a&gt;, the asset pipeline in Rails etc) to automate the addition of prefixes to each of the CSS classes.&lt;/p&gt;
&lt;p&gt;Another tip is to avoid using Bootstrap CSS classes directly. Instead, use the “extend” functionality in your preprocessor (Sass, Less, Stylus etc) of choice. For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  .ep-btn {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @extend .btn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;amp;:hover {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          @extend .btn:hover
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This way your project can extend the Bootstrap styles but keep your customizations separate and not closely coupled to the framework.&lt;/p&gt;
&lt;p&gt;The same logic should also be applied to the JavaScript in your project. Rather than using the Bootstrap class names as hooks in your JavaScript code, use a prefix (e.g. js-btn) or use HTML5 data attributes. Separating the hooks used for CSS styles from those used in JavaScript is very helpful when upgrading or swapping out a client-side framework like Bootstrap.&lt;/p&gt;
&lt;h3 id=&#34;test-all-of-the-things&#34;&gt;Test All Of The Things&lt;/h3&gt;
&lt;p&gt;Pamela wrapped up the talk by explaining how testing front end code would ease the pain of upgrading a library next time. There are many testing libraries available today which address some of these concerns. She mentioned &lt;a href=&#34;https://mochajs.org/&#34;&gt;mocha&lt;/a&gt;, &lt;a href=&#34;http://www.chaijs.com/&#34;&gt;Chai&lt;/a&gt;, &lt;a href=&#34;https://github.com/jsdom/jsdom&#34;&gt;jsdom&lt;/a&gt; and &lt;a href=&#34;https://docs.seleniumhq.org/&#34;&gt;Selenium&lt;/a&gt; which all look very helpful. In addition to testing front end code she offered up the idea of “diffing your front end” in a visual way. This concept was very interesting to someone who ensures designs are consistent across a wide array of browsers and devices on a daily basis.
&lt;img alt=&#34;Diff your front end&#34; border=&#34;0&#34; height=&#34;341&#34; src=&#34;/blog/2013/06/css-conf-2013-when-bootstrap-attacks/image-1.png&#34; style=&#34;display:block; margin-left:auto; margin-right:auto;&#34; title=&#34;diff-your-front-end.png&#34; width=&#34;559&#34;/&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/python-needle/needle&#34;&gt;Needle&lt;/a&gt; is a tool which allows you to do this &lt;em&gt;automatically&lt;/em&gt;. Once you develop a test case, you can run Needle to view a visual diff of your CSS changes. I think this is an excellent idea. Pamela also noted that the combination of &lt;a href=&#34;https://support.mozilla.org/en-US/questions/940991&#34;&gt;Firefox screenshots&lt;/a&gt; and &lt;a href=&#34;https://www.kaleidoscopeapp.com/&#34;&gt;Kaleidoscope&lt;/a&gt; could be used manually in much the same way.&lt;/p&gt;
&lt;p&gt;Many thanks to &lt;a href=&#34;https://twitter.com/pamelafox&#34;&gt;Pamela&lt;/a&gt; for sharing this! The slides for this talk can be viewed &lt;a href=&#34;http://slides.com/pamelafox/when-bootstrap-attacks#/&#34;&gt;here&lt;/a&gt; and the talk was recorded so the video will also be available sometime soon.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Building Xpdf on Ubuntu</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2011/08/building-xpdf-on-ubuntu/"/>
      <id>https://www.endpointdev.com/blog/2011/08/building-xpdf-on-ubuntu/</id>
      <published>2011-08-31T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;It may happen that you need to use &lt;a href=&#34;https://www.xpdfreader.com/&#34;&gt;Xpdf&lt;/a&gt;, even though it no longer ships with &lt;a href=&#34;https://www.ubuntu.com/&#34;&gt;Ubuntu&lt;/a&gt; and is considered &amp;hellip; outdated? buggy? insecure? In any case, it still renders some PDFs that &lt;a href=&#34;https://poppler.freedesktop.org/&#34;&gt;Poppler&lt;/a&gt;-based viewers such as &lt;a href=&#34;https://wiki.gnome.org/Apps/Evince&#34;&gt;Evince&lt;/a&gt; don’t, or allows some troublesome PDFs to print as fonts and line art instead of a rasterized mess.&lt;/p&gt;
&lt;p&gt;Here’s how I built and installed xpdf 3.02 on Ubuntu 11.04 (Natty Narwhal) x86_64:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt-get install libfreetype6-dev libmotif-dev
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;wget ftp://ftp.foolabs.com/pub/xpdf/xpdf-3.02.tar.gz  &lt;span style=&#34;color:#888&#34;&gt;# now 3.03 is current&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tar xzpf xpdf-3.02.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;cd&lt;/span&gt; xpdf-3.02
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;./configure --with-freetype2-library=/usr/lib/x86_64-linux-gnu &lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;&lt;/span&gt;    --with-freetype2-includes=/usr/include/freetype2 &lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;&lt;/span&gt;    --with-Xm-library=/usr/lib &lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;&lt;/span&gt;    --with-Xm-includes=/usr/include/Xm
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;make
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# see lots of warnings!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo make install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That’s it. Not as nice as the old native Debian/Ubuntu packages, but gets the job done.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Paperclip in Spree: Extending Product Image Sizes</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2011/06/paperclip-spree-overriding-product/"/>
      <id>https://www.endpointdev.com/blog/2011/06/paperclip-spree-overriding-product/</id>
      <published>2011-06-06T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;Spree uses the popular gem &lt;a href=&#34;https://github.com/thoughtbot/paperclip&#34;&gt;Paperclip&lt;/a&gt; for assigning images as attachments to products. The basic installation requires you to install the gem, create a migration to store the paperclip-specific fields in your model, add the &lt;strong&gt;has_attached_file&lt;/strong&gt; information to the model with the attachment, add the ability to upload the file, and display the file in a view. In Spree, the Image model has an attached file with the following properties:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Image&lt;/span&gt; &amp;lt; &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Asset&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  has_attached_file &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:attachment&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:styles&lt;/span&gt; =&amp;gt; { &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:mini&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;48x48&amp;gt;&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:small&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;100x100&amp;gt;&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:product&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;240x240&amp;gt;&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:large&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;600x600&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:default_style&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:product&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:url&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/assets/products/:id/:style/:basename.:extension&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:path&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;:rails_root/public/assets/products/:id/:style/:basename.:extension&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see, when an admin uploads an image, four image sizes are created: large, product, small, and mini.&lt;/p&gt;
&lt;p&gt;
&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5615185163090719650&#34; src=&#34;/blog/2011/06/paperclip-spree-overriding-product/image-0.jpeg&#34; style=&#34;width: 160px;&#34;/&gt;
&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5615185163090719650&#34; src=&#34;/blog/2011/06/paperclip-spree-overriding-product/image-0.jpeg&#34; style=&#34;width: 120px;&#34;/&gt;
&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5615185163090719650&#34; src=&#34;/blog/2011/06/paperclip-spree-overriding-product/image-0.jpeg&#34; style=&#34;width: 100px;&#34;/&gt;
&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5615185163090719650&#34; src=&#34;/blog/2011/06/paperclip-spree-overriding-product/image-0.jpeg&#34; style=&#34;width: 48px;&#34;/&gt;&lt;br&gt;
Four images are created per product image uploaded in Spree (Note: not to scale).
&lt;/p&gt;
&lt;p&gt;Last week, I wanted to add several additional sizes to be created upon upload to improve performance. This involved several steps, described below.&lt;/p&gt;
&lt;h3 id=&#34;step-1-extend-attachment_definitions&#34;&gt;Step 1: Extend attachment_definitions&lt;/h3&gt;
&lt;p&gt;First, I had to override the image attachment styles, with the code shown below. My application is running on Spree 0.11.2 (Rails 2.3.*), so this was added inside the extension activate method, but in Rails 3.0 versions of Spree, this would be added inside the engine’s activate method.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Image&lt;/span&gt;.attachment_definitions[&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:attachment&lt;/span&gt;][&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:styles&lt;/span&gt;].merge!(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:newsize1&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;200x200&amp;gt;&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:newsize2&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;284x284&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;step-2-add-image-helper-methods&#34;&gt;Step 2: Add Image Helper Methods&lt;/h3&gt;
&lt;p&gt;Spree has the following bit of code in its base_helper.rb, which in theory should create methods for calling each image (mini_image, small_image, product_image, large_image, newsize1_image, newsize2_image):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Image&lt;/span&gt;.attachment_definitions[&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:attachment&lt;/span&gt;][&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:styles&lt;/span&gt;].each &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt; |style, v|
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    define_method &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;#{&lt;/span&gt;style&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;_image&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt; |product, *options|
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      options = options.first || {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; product.images.empty?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image_tag &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;noimage/&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;#{&lt;/span&gt;style&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;.jpg&amp;#34;&lt;/span&gt;, options
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image = product.images.first
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options.reverse_merge! &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:alt&lt;/span&gt; =&amp;gt; image.alt.blank? ? product.name : image.alt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image_tag image.attachment.url(style), options
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But for some reason in this application, perhaps based on order of extension evaluation, this was only applied to the original image sizes. I remedied this by adding the following code to my extension base helper:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  [&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:newsize1&lt;/span&gt;, newsize2].each &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt; |style|
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    define_method &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;#{&lt;/span&gt;style&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;_image&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt; |product, *options|
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      options = options.first || {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; product.images.empty?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image_tag &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;noimage/&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;#{&lt;/span&gt;style&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;.jpg&amp;#34;&lt;/span&gt;, options
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image = product.images.first
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options.reverse_merge! &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:alt&lt;/span&gt; =&amp;gt; image.alt.blank? ? product.name : image.alt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        image_tag image.attachment.url(style), options
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;step-3-create-cropped-images-for-existing-images&#34;&gt;Step 3: Create Cropped Images for Existing Images&lt;/h3&gt;
&lt;p&gt;Finally, instead of requiring all images to be re-uploaded to create the new cropped images, I wrote a quick bash script to generate images with the new sizes. This script was placed inside the RAILS_ROOT/public/assets/products/ directory, where product images are stored. The script iterates through each existing directory and creates cropped images based on the original uploaded image with the ImageMagick command-line tool, which is what Paperclip uses for resizing.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c00;font-weight:bold&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c00;font-weight:bold&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; i in &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;`&lt;/span&gt;ls */original/*&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;image_name&lt;/span&gt;=&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;i&lt;/span&gt;#*original&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\/&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;dir_name&lt;/span&gt;=&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;i&lt;/span&gt;/&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\/&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;original&lt;/span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\/&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$image_name&lt;/span&gt;/&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    mkdir &lt;span style=&#34;color:#369&#34;&gt;$dir_name&lt;/span&gt;/newsize1/ &lt;span style=&#34;color:#369&#34;&gt;$dir_name&lt;/span&gt;/newsize2/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    convert &lt;span style=&#34;color:#369&#34;&gt;$i&lt;/span&gt; -resize &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;200x200&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$dir_name&lt;/span&gt;/newsize1/&lt;span style=&#34;color:#369&#34;&gt;$image_name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    convert &lt;span style=&#34;color:#369&#34;&gt;$i&lt;/span&gt; -resize &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;284x284&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$dir_name&lt;/span&gt;/newsize2/&lt;span style=&#34;color:#369&#34;&gt;$image_name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#038&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;created images for &lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$i&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;step-4-update-views&#34;&gt;Step 4: Update Views&lt;/h3&gt;
&lt;p&gt;Finally, I added newsize1_image and newsize2_image methods throughout the views, e.g.:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;%= link_to newsize1_image(product), product %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;%= link_to newsize2_image(taxon.products.first), seo_url(taxon) %&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;It would be ideal to remove Step 2 described here by investigating why the image methods are not defined by the Spree core BaseHelper module. It’s possible that this is working as expected on more recent versions of Spree. Other than that violation of the DRY principle, it is a fairly simple process to extend the Paperclip image settings to include additional sizes.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Determining dominant image color</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2011/04/determining-dominant-image-color/"/>
      <id>https://www.endpointdev.com/blog/2011/04/determining-dominant-image-color/</id>
      <published>2011-04-21T00:00:00+00:00</published>
      <author>
        <name>Jeff Boes</name>
      </author>
      <content type="html">
        &lt;p&gt;This grew out of a misunderstanding of a client’s request, so it never saw the light of day, but I thought it was an interesting problem.&lt;/p&gt;
&lt;p&gt;The request was to provide a “color search” of products, i.e., “show me all the orange products”. Finding the specific products was not a challenge since it was just a database query. Instead I was interested in how to choose a “representative image” from among the available images for that product. (And as it turns out, the image filename gave me that information, but let’s assume you don’t have that luxury: how do you tell, from a group of images, which one is “more orange” than the others?)&lt;/p&gt;
&lt;p&gt;Of course, this depends on the composition of the image. In this case, I knew that the majority were of solid-color (or two- or three-color at most) products on a white background. The approach that was settled on was to severely pixellate the image into something like 20x20 (arbitrary; this could be very dependent on the images under study, or the graphics library in use). If you also supply a color palette restricted to the colors you are interested in matching (e.g., primary, and secondary colors, plus perhaps black, white, and gray), you would have a roster of the colors represented.&lt;/p&gt;
&lt;p&gt;Another approach makes use of the powerful ImageMagick library. There’s a huge list of examples and instructions at &lt;a href=&#34;https://www.imagemagick.org/Usage/quantize/&#34;&gt;https://www.imagemagick.org/Usage/quantize/&lt;/a&gt;, but for my purposes this short sample will do:&lt;/p&gt;
&lt;img height=&#34;117px;&#34; src=&#34;/blog/2011/04/determining-dominant-image-color/image-0.jpeg&#34; width=&#34;156px;&#34;/&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ convert Waffle.jpg -scale 1x1\! -format &amp;#39;%[pixel:u]&amp;#39; info:-
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rgb(219,166,94)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;img height=&#34;37px;&#34; src=&#34;/blog/2011/04/determining-dominant-image-color/image-1.png&#34; width=&#34;48px;&#34;/&gt;
&lt;p&gt;Here we reduce an image to a single pixel, then report the RGB value of that color. After that it’s just a matter of determining how close this “average” is to your desired color.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>(Image|Graphics)Magick trick for monitoring or visualizations</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2010/11/imagegraphicsmagick-trick-for/"/>
      <id>https://www.endpointdev.com/blog/2010/11/imagegraphicsmagick-trick-for/</id>
      <published>2010-11-03T00:00:00+00:00</published>
      <author>
        <name>Kiel Christofferson</name>
      </author>
      <content type="html">
        &lt;p&gt;It’s a good time for all when we start poking fun at the visual assault of stereotypical PowerPoint presentations. On the other hand, when data is presented in an effective visual format, human brains are able to quickly grasp the ideas involved and pick out important pieces of information, such as “outliers”.&lt;/p&gt;
&lt;p&gt;Without getting into a long trumpeting session about the usefulness of data visualization (there are plenty of &lt;a href=&#34;https://www.amazon.com/s?url=search-alias%3Daps&amp;amp;field-keywords=Data+Visualization&#34;&gt;books&lt;/a&gt; on the subject), I’d like to jump directly into a Magick trick or two for creating simple visualizations.&lt;/p&gt;
&lt;p&gt;Let’s imagine we’ve got a group of machines &lt;a href=&#34;https://www.visionport.com/&#34;&gt;serving a particular purpose&lt;/a&gt;. Now let’s say I want quick insight into not only the internal activity of all 8 machines, but also what the systems believe they are sending to their displays.&lt;/p&gt;
&lt;p&gt;With a little magick (of the ImageMagick or GraphicsMagick variety), we can save ourselves from running “ps” and “free” and from having to be in the same room (or the same country) as the system we’re checking up on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First&lt;/strong&gt;, let’s organize some simple output from the system:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#038&#34;&gt;echo&lt;/span&gt; -en &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;$(&lt;/span&gt;hostname&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; GPID: &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;$(&lt;/span&gt; pgrep googleearth-bin &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;. APPID: &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;$(&lt;/span&gt; pgrep -u root -f sbin/apache2 &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;.\nCRASH: &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;$(&lt;/span&gt; ls -1 &lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;HOME&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;/.googleearth/crashlogs/ | wc -l &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;. MEMF: &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;$(&lt;/span&gt; awk &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/^MemFree/ {print $2$3}&amp;#39;&lt;/span&gt; /proc/meminfo &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;.&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Which gives us output something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lg1 - GPID: 5265. APPID: 10452.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CRASH: 3. MEMF: 4646240kB.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cool, but we want to combine this with the imagery supposedly being currently displayed by X. So, we turn it into an image that we can overlay, like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#038&#34;&gt;echo&lt;/span&gt; -en &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;$(&lt;/span&gt;hostname&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; GPID: &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;$(&lt;/span&gt; pgrep googleearth-bin &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;. APPID: &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;$(&lt;/span&gt; pgrep -u root -f sbin/apache2 &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;.\nCRASH: &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;$(&lt;/span&gt; ls -1 &lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;HOME&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;/.googleearth/crashlogs/ | wc -l &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;. MEMF: &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;$(&lt;/span&gt; awk &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/^MemFree/ {print $2$3}&amp;#39;&lt;/span&gt; /proc/meminfo &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;.&amp;#34;&lt;/span&gt; | &lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;&lt;/span&gt;convert -pointsize &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;18&lt;/span&gt; -background &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#00000080&amp;#39;&lt;/span&gt; -fill white text:- -trim -bordercolor &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#00000080&amp;#39;&lt;/span&gt; -border 5x5 miff:/tmp/text&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is one long command and might be hard to read, but it is simply using “convert” to turn the text output into a semi-transparent “miff” image for later use. It would be very easy to put the stat collection into a script on each host, but we’re just going with quick and dirty at the moment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second&lt;/strong&gt;, let’s get our little overlay image composited with a screenshot from X:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ DISPLAY=:0 import -window root miff:- | composite -gravity south -geometry +0+3 miff:/tmp/text miff:- -resize 600 miff:/tmp/$(hostname).miff&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So, in a single pipeline we imported a screenshot of the root window, then used “composite” to overlay our semi-transparent stats image and resize the whole thing to be a bit more manageable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Finally&lt;/strong&gt;, we want to perform these things across all the systems and be left with something we can quickly glance at to see if there are obvious problems. So, let’s create a quick shell loop and execute our commands via ssh, placing the resize/​composite burden on the shoulders of each individual system (be sure to escape variables for remote interpolation!):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c00;font-weight:bold&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c00;font-weight:bold&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#collect data first&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; system in &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;`&lt;/span&gt;seq &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt; 8&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;`&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; ssh user@&lt;span style=&#34;color:#369&#34;&gt;$system&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;echo -en \&amp;#34;\$(hostname) GPID: \$( pgrep googleearth-bin ). APPID: \$( pgrep -u root -f sbin/apache2 ).\nCRASH: \$( ls -1 \${HOME}/.googleearth/crashlogs/ | wc -l ). MEMF: \$( awk &amp;#39;/^MemFree/ {print \$2\$3}&amp;#39; /proc/meminfo ).&amp;#34;&lt;/span&gt; | &lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;&lt;/span&gt;convert -pointsize &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;18&lt;/span&gt; -background &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#00000080&amp;#39;&lt;/span&gt; -fill white text:- -trim -bordercolor &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#00000080&amp;#39;&lt;/span&gt; -border 5x5 miff:/tmp/text;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;DISPLAY&lt;/span&gt;=:0 import -window root miff:- | &lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#04d;background-color:#fff0f0&#34;&gt;&lt;/span&gt;composite -gravity south -geometry +0+3 miff:/tmp/text miff:- -resize &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;600&lt;/span&gt; miff:-&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34; &amp;gt;/tmp/system&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;system&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;.miff;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;#make a montage of the data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;montage -monitor -background black -tile 8x1 -geometry +5+0 \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; /tmp/system{6,7,8,1,2,3,4,5}.miff \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; /tmp/system-montage.png &amp;amp;&amp;amp; rm -f /tmp/system?.miff&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With something so simple, we can quickly view from New York what’s happening on systems installed in &lt;a href=&#34;https://www.thetech.org/&#34;&gt;California&lt;/a&gt;, like so:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2010/11/imagegraphicsmagick-trick-for/system-montage.png&#34;&gt;&lt;img alt=&#34;montage example&#34; height=&#34;300&#34; src=&#34;/blog/2010/11/imagegraphicsmagick-trick-for/system-montage.png&#34; width=&#34;1350&#34;/&gt;&lt;/a&gt;&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>JPEG compression: quality or quantity?</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2009/12/jpeg-compression-quality-or-quantity/"/>
      <id>https://www.endpointdev.com/blog/2009/12/jpeg-compression-quality-or-quantity/</id>
      <published>2009-12-24T00:00:00+00:00</published>
      <author>
        <name>Daniel Browning</name>
      </author>
      <content type="html">
        &lt;p&gt;There are many aspects of JPEG files that are interesting to web site developers, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The optimal trade off between quality and file size for any encoder and uncompressed source image.&lt;/li&gt;
&lt;li&gt;Reducing size of an existing JPEG image when the uncompressed source is unavailable, but still finding the same optimal trade-off.&lt;/li&gt;
&lt;li&gt;Comparison of different encoders and/or settings for quality at a given file size.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two essential factors are file size and image quality. Bytes are objectively measurable, but image quality is much more nebulous. What to one person is a perfectly acceptable image is to another a grotesque abomination of artifacts. So the quality factor is subjective. For example, Steph sent me some images to compare compression artifacts. Here is the first one with three different settings in ImageMagick: 95, 50, and 8:&lt;/p&gt;
&lt;table cellpadding=&#34;0&#34; cellspacing=&#34;2&#34;&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td valign=&#34;top&#34;&gt;
&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-0.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-0.jpeg&#34; title=&#34;size: 27K setting: 95&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td valign=&#34;top&#34;&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-1.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-1.jpeg&#34; title=&#34;size: 8.0K setting: 50&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td valign=&#34;top&#34;&gt;
&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-2.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-2.jpeg&#34; title=&#34;size: 2.9K setting: 8&#34;/&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Compare the subtle (or otherwise) differences in the following images (mouseover shows the filesize and compression setting):&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-3.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-3.jpeg&#34; title=&#34;size: 30K setting: 95&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-4.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-4.jpeg&#34; title=&#34;size: 17K setting: 85&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-5.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-5.jpeg&#34; title=&#34;size: 7.7K setting: 50&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-6.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-6.jpeg&#34; title=&#34;size: 4.1K setting: 20&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Mouseover each image for the file size and ImageMagick compression setting. Additional comparisons are below. Each image can be opened in a separate browser tab for easy A/B comparison. I think many would find the setting of 8 to have too many artifacts, even though it’s 10 times smaller than image compressed at a setting of 95. Some would find the setting of 50 to be an acceptable tradeoff between quality and size, since it sends 3.4 times fewer bytes.&lt;/p&gt;
&lt;p&gt;Here is the code I wrote to make the comparison (shell script is great for this stuff):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c00;font-weight:bold&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c00;font-weight:bold&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;HTML_OUTFILE&lt;/span&gt;=comparison.html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&amp;lt;html&amp;gt;&amp;#39;&lt;/span&gt; &amp;gt; &lt;span style=&#34;color:#369&#34;&gt;$HTML_OUTFILE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;write_img_html () {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;size&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;`&lt;/span&gt;du -h --apparent-size &lt;span style=&#34;color:#369&#34;&gt;$1&lt;/span&gt; | cut -f 1&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; [ -n &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$2&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt; ]; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#369&#34;&gt;qual&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;setting: &lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$2&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cat &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;lt;&amp;lt;EOF &amp;gt;&amp;gt;$HTML_OUTFILE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;lt;a href=&amp;#34;$1&amp;#34;&amp;gt;&amp;lt;img src=&amp;#34;$1&amp;#34; title=&amp;#34;size: $size $qual&amp;#34;&amp;gt;&amp;lt;/a&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; name in image1 image2; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;orig&lt;/span&gt;=&lt;span style=&#34;color:#369&#34;&gt;$name&lt;/span&gt;-original.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;resized&lt;/span&gt;=&lt;span style=&#34;color:#369&#34;&gt;$name&lt;/span&gt;-300.png
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#038&#34;&gt;echo&lt;/span&gt; Resizing &lt;span style=&#34;color:#369&#34;&gt;$orig&lt;/span&gt; to &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;300&lt;/span&gt; on longest side: &lt;span style=&#34;color:#369&#34;&gt;$resized&lt;/span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    convert &lt;span style=&#34;color:#369&#34;&gt;$orig&lt;/span&gt; -resize 300x300 &lt;span style=&#34;color:#369&#34;&gt;$resized&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    write_img_html &lt;span style=&#34;color:#369&#34;&gt;$resized&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;lossless&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; quality in &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;100&lt;/span&gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;95&lt;/span&gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;85&lt;/span&gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;50&lt;/span&gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;20&lt;/span&gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;8&lt;/span&gt; 1; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#038&#34;&gt;echo&lt;/span&gt; Creating JPEG quality &lt;span style=&#34;color:#369&#34;&gt;$quality&lt;/span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#369&#34;&gt;jpeg&lt;/span&gt;=&lt;span style=&#34;color:#369&#34;&gt;$name&lt;/span&gt;-300-q-&lt;span style=&#34;color:#369&#34;&gt;$quality&lt;/span&gt;.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        convert &lt;span style=&#34;color:#369&#34;&gt;$resized&lt;/span&gt; -strip -quality &lt;span style=&#34;color:#369&#34;&gt;$quality&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$jpeg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        write_img_html &lt;span style=&#34;color:#369&#34;&gt;$jpeg&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$quality&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Another factor that often comes into play is how artifacts in the image (e.g. aliasing, ringing, noise) combine with JPEG compression artifacts to exacerbate quality problems. So one way to get smaller file sizes is to reduce the other types of artifacts in the image, thereby allowing higher JPEG compression.&lt;/p&gt;
&lt;p&gt;The most common source of artifacts is image resizing. If you are resizing the images, I strongly recommend using a program that has a high quality filter. Irfanview and ImageMagick are two good choices.&lt;/p&gt;
&lt;p&gt;The ideal situation is this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uncompressed source image&lt;/li&gt;
&lt;li&gt;Full-resolution if you will be handling the resize&lt;/li&gt;
&lt;li&gt;Absent artifacts such as aliasing&lt;/li&gt;
&lt;li&gt;Resize performed with good software like ImageMagick&lt;/li&gt;
&lt;li&gt;JPEG compression chosen based on subjective quality assessment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Choosing the trade-off between quality and file size is difficult in part because it varies by image content. Images with lots of small color details (e.g. bright fabric threads; AKA high spatial frequency chroma) stand less compression than images that only have medium sized details that do not have important  and minute color information.&lt;/p&gt;
&lt;p&gt;One of the settings that is important for small web images is removal of the color space profile (e.g. sRGB). The only time it is needed is when there is a good reason for using non-sRGB JPEG, such as when you are certain that your users will have color managed browsers. Removing it can shave off 5KB or so; software will assume images without profiles have an sRGB profile. It can be removed with the -strip parameter of ImageMagick.&lt;/p&gt;
&lt;p&gt;As for choosing the specific compression settings, keep in mind that there are over 30 different types of options/techniques that can be used in compressing the image. Most image programs simplify that to a sliding scale from 0 to 100, 1 to 12, or something else. Keep in mind that even when programs use the same scale (e.g. 0 to 100), they probably have different ideas of what the numbers mean. 95 in one program may be very different than 95 in another.&lt;/p&gt;
&lt;p&gt;If bandwidth is not an issue, then I use a setting of 95 on ImageMagick, because in normal images I can’t tell the difference between 95 and 100. But when file size in an important concern, I consider 85 to be the optimal setting. In this image, the difference should be clear, but I generally find that cutting filesize in half is worth it. Below 85, the artifacts are too onerous for my taste.&lt;/p&gt;
&lt;p&gt;You don’t often hear about web site visitors’ dissatisfaction with compression artifacts, so you might be tempted to just reduce file sizes even beyond the point where it gets noticable. But I think there is a subliminal effect from the reduced image quality. Visitors may not stop visiting the site immediately, but my gut feeling is it leaves them with a certain impression in their mind or taste in their mouth. I would guess that user testing might result in comments such as “the X web site is not the same high-grade quality as the Y web site”, even if they don’t put it into words as specific as “the compression artifacts make X look uglier than Y”. Even if that pet theory is true, it still has to be balanced against the benefit of faster page loading times.&lt;/p&gt;
&lt;p&gt;Ideally, the tradeoff between quality and page loading time would be a choice left to the user. Those who prefer fewer artifacts could set their browser to download larger, less-compressed image files than the default, while users with low bandwidth could set it for more compressed images to get a faster page load at the expense of quality. I could imagine an Apache module and corresponding Firefox add-on some day.&lt;/p&gt;
&lt;p&gt;Regarding the situation where you want to reduce the file size of existing JPEGs, my advice is to first try (hard) to get the original source files. You can do better (for any given quality/size tradeoff) from those than you can by just manipulating the existing files. If that’s not possible, then the suboptimal workflows like jpegtran, jpegoptim, and doing a full decompress/recompress are the only alternative.&lt;/p&gt;
&lt;p&gt;As far as comparing different encoders, I haven’t really looked into that except to compare ImageMagick and Photoshop, where I (subjectively) determined they both had about similar quality for file size (and vice-versa).&lt;/p&gt;
&lt;p&gt;Here are all the comparison images. The file size and ImageMagick quality setting are in the rollover. I suggest opening images in browser tabs for easy A/B comparison.&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-7.png&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-7.png&#34; title=&#34;size: 87K setting: lossless&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-8.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-8.jpeg&#34; title=&#34;size: 74K setting: 100&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-0.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-0.jpeg&#34; title=&#34;size: 27K setting: 95&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-10.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-10.jpeg&#34; title=&#34;size: 15K setting: 85&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-1.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-1.jpeg&#34; title=&#34;size: 8.0K setting: 50&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-12.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-12.jpeg&#34; title=&#34;size: 4.8K setting: 20&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-2.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-2.jpeg&#34; title=&#34;size: 2.9K setting: 8&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-14.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-14.jpeg&#34; title=&#34;size: 1.7K setting: 1&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-15.png&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-15.png&#34; title=&#34;size: 87K setting: lossless&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-16.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-16.jpeg&#34; title=&#34;size: 80K setting: 100&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-3.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-3.jpeg&#34; title=&#34;size: 30K setting: 95&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-4.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-4.jpeg&#34; title=&#34;size: 17K setting: 85&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-5.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-5.jpeg&#34; title=&#34;size: 7.7K setting: 50&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-6.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-6.jpeg&#34; title=&#34;size: 4.1K setting: 20&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-21.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-21.jpeg&#34; title=&#34;size: 2.2K setting: 8&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-22.jpeg&#34;&gt;&lt;img src=&#34;/blog/2009/12/jpeg-compression-quality-or-quantity/image-22.jpeg&#34; title=&#34;size: 1.3K setting: 1&#34;/&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

      </content>
    </entry>
  
</feed>
