Image Files and Size

Describe some of the meta data and considerations when managing Image files. Describe how these relate to Data Compression …

  • File Type, PNG and JPG are two types used in this lab
    • Different file types can affect how much storage you use which comes in handy when managing image size. Also, both use different compression algorithms meaning that they compress images more or less than others
  • Size, height and width, number of pixels
    • Size, height, width, & resolution also affects the image size aswell as whether or not an image will fit on a webpage
  • Visual perception, lossy compression
    • Compression methods help to make an image smaller although they may sacrifice quality. It is in the programmer’s inteterest to decide what to compromise on. Visual perception is the brains ability to make sense of an image. You don’t want something unrecognizable yet too large of a file.

pathlib

File paths are different on Windows versus Mac and Linux. This can cause problems in a project as you work and deploy on different Operating Systems (OS’s), pathlib is a solution to this problem.

  • What are commands you use in terminal to access files?
    • You can use CD in both mac, linux, and windows (POSH)
  • What are the command you use in Windows terminal to access files?
    • You can use ii on POSH or nautilus for linux based system (depends on file manager though)
  • What are some of the major differences?
    • Major differences are how files are structured & organized but commands to the exact same thing

Provide what you observed, struggled with, or leaned while playing with this code.

  • Why is path a big deal when working with images?
    • Path is important to make sure you are referencing the same image.
  • How does the meta data source and label relate to Unit 5 topics?
    • Meta data includes details relevant to an image and its production.
  • Look up IPython, describe why this is interesting in Jupyter Notebooks for both Pandas and Images?
    • IPython is a kernel for Jupyter notbooks. It has GUI toolkits to load images and IPython can interperate Pandas (language built on Python)

from IPython.display import Image, display
from pathlib import Path  # https://medium.com/@ageitgey/python-3-quick-tip-the-easy-way-to-deal-with-file-paths-on-windows-mac-and-linux-11a072b58d5f

# prepares a series of images
def image_data(path=Path("images/"), images=None):  # path of static images is defaulted
    if images is None:  # default image
        images = [
            {'source': "Peter Carolin", 'label': "Clouds Impression", 'file': "clouds-impression.png"},
            {'source': "Peter Carolin", 'label': "Lassen Volcano", 'file': "lassen-volcano.jpg"}
        ]
    for image in images:
        # File to open
        image['filename'] = path / image['file']  # file with path
    return images

def image_display(images):
    for image in images:  
        display(Image(filename=image['filename']))


# Run this as standalone tester to see sample data printed in Jupyter terminal
if __name__ == "__main__":
    # print parameter supplied image
    green_square = image_data(images=[{'source': "Internet", 'label': "Green Square", 'file': "green-square-16.png"}])
    image_display(green_square)
    
    # display default images from image_data()
    default_images = image_data()
    image_display(default_images)
    

Reading and Encoding Images (2 implementations follow)

base64

  • How is Base64 similar or different to Binary and Hexadecimal?
    • Base64 is a different method of encoding information which both binary & hex do
  • Translate first 3 letters of your name to Base64.
    • aas = YQ==

io, BytesIO

  • Where have you been a consumer of buffering?
    • Buffering can be seen, for example, when loading online video content and it takes time to load
  • From your consumer experience, what effects have you experienced from buffering?
    • Effects can be an unsatisfactory user experience and diminished viewership
  • How do these effects apply to images?
    • Buffering can happen if images are too large to load quickly. You choose between quality or size. Either choose a balance, or choose more on the size aspect.

Data Structures, Imperative Programming Style, and working with Images

  • Does this code seem like a series of steps are being performed?
    • Yes it is.
  • Describe Grey Scale algorithm in English or Pseudo code?
    • Open an image
    • Convert image data to numbers
      • Average pixel values (algorithm to convert to greyscale)
    • Convert back to image format & display
  • Describe scale image?
    • Scale image is same resolution but just different size.
  • Is scale image a type of compression? If so, line it up with College Board terms described?
    • No it is not a method of compression. Your webpage will still load the same amound of pixels
from IPython.display import HTML, display
from pathlib import Path  # https://medium.com/@ageitgey/python-3-quick-tip-the-easy-way-to-deal-with-file-paths-on-windows-mac-and-linux-11a072b58d5f
from PIL import Image as pilImage # as pilImage is used to avoid conflicts
from io import BytesIO
import base64
import numpy as np

# prepares a series of images
def image_data(path=Path("images/"), images=None):  # path of static images is defaulted
    if images is None:  # default image
        images = [
            {'source': "Internet", 'label': "Green Square", 'file': "green-square-16.png"},
            {'source': "Peter Carolin", 'label': "Clouds Impression", 'file': "clouds-impression.png"},
            {'source': "Peter Carolin", 'label': "Lassen Volcano", 'file': "lassen-volcano.jpg"}
        ]
    for image in images:
        # File to open
        image['filename'] = path / image['file']  # file with path
    return images

# Large image scaled to baseWidth of 320
def scale_image(img):
    baseWidth = 320
    scalePercent = (baseWidth/float(img.size[0]))
    scaleHeight = int((float(img.size[1])*float(scalePercent)))
    scale = (baseWidth, scaleHeight)
    return img.resize(scale)

# PIL image converted to base64
def image_to_base64(img, format):
    with BytesIO() as buffer:
        img.save(buffer, format)
        return base64.b64encode(buffer.getvalue()).decode()

# Set Properties of Image, Scale, and convert to Base64
def image_management(image):  # path of static images is defaulted        
    # Image open return PIL image object
    img = pilImage.open(image['filename'])
    
    # Python Image Library operations
    image['format'] = img.format
    image['mode'] = img.mode
    image['size'] = img.size
    # Scale the Image
    img = scale_image(img)
    image['pil'] = img
    image['scaled_size'] = img.size
    # Scaled HTML
    image['html'] = '<img src="data:image/png;base64,%s">' % image_to_base64(image['pil'], image['format'])
    
# Create Grey Scale Base64 representation of Image
def image_management_add_html_grey(image):
    # Image open return PIL image object
    img = image['pil']
    format = image['format']
    
    img_data = img.getdata()  # Reference https://www.geeksforgeeks.org/python-pil-image-getdata/
    image['data'] = np.array(img_data) # PIL image to numpy array
    image['gray_data'] = [] # key/value for data converted to gray scale

    # 'data' is a list of RGB data, the list is traversed and hex and binary lists are calculated and formatted
    for pixel in image['data']:
        # create gray scale of image, ref: https://www.geeksforgeeks.org/convert-a-numpy-array-to-an-image/
        average = (pixel[0] + pixel[1] + pixel[2]) // 3  # average pixel values and use // for integer division
        if len(pixel) > 3:
            image['gray_data'].append((average, average, average, pixel[3])) # PNG format
        else:
            image['gray_data'].append((average, average, average))
        # end for loop for pixels
        
    img.putdata(image['gray_data'])
    image['html_grey'] = '<img src="data:image/png;base64,%s">' % image_to_base64(img, format)


# Jupyter Notebook Visualization of Images
if __name__ == "__main__":
    # Use numpy to concatenate two arrays
    images = image_data()
    
    # Display meta data, scaled view, and grey scale for each image
    for image in images:
        image_management(image)
        print("---- meta data -----")
        print(image['label'])
        print(image['source'])
        print(image['format'])
        print(image['mode'])
        print("Original size: ", image['size'])
        print("Scaled size: ", image['scaled_size'])
        
        print("-- original image --")
        display(HTML(image['html'])) 
        
        print("--- grey image ----")
        image_management_add_html_grey(image)
        display(HTML(image['html_grey'])) 
    print()
---- meta data -----
Green Square
Internet
PNG
RGBA
Original size:  (16, 16)
Scaled size:  (320, 320)
-- original image --

--- grey image ----

---- meta data -----
Clouds Impression
Peter Carolin
PNG
RGBA
Original size:  (320, 234)
Scaled size:  (320, 234)
-- original image --

--- grey image ----

---- meta data -----
Lassen Volcano
Peter Carolin
JPEG
RGB
Original size:  (2792, 2094)
Scaled size:  (320, 240)
-- original image --

--- grey image ----

Additionally, review all the imports in these three demos. Create a definition of their purpose, specifically these …

  • PIL
    • PIL is Python Imaging Library. It allows you to open, manipulate, and save images in a variety of formats. It is useful for displaying things and modifying these images.
  • numpy
    • Numpy is a useful library for computing and mathematics. You can use it to write algorithms and do complex mathematics.
  • base64
    • Base64 allows you to encode and decode base64 data. You can encode binary data as printable text and can serve as a basic way to obscure data.
from IPython.display import HTML, display
from pathlib import Path  # https://medium.com/@ageitgey/python-3-quick-tip-the-easy-way-to-deal-with-file-paths-on-windows-mac-and-linux-11a072b58d5f
from PIL import Image as pilImage # as pilImage is used to avoid conflicts
from io import BytesIO
import base64
import numpy as np


class Image_Data:

    def __init__(self, source, label, file, path, baseWidth=320):
        self._source = source    # variables with self prefix become part of the object, 
        self._label = label
        self._file = file
        self._filename = path / file  # file with path
        self._baseWidth = baseWidth

        # Open image and scale to needs
        self._img = pilImage.open(self._filename)
        self._format = self._img.format
        self._mode = self._img.mode
        self._originalSize = self.img.size
        self.scale_image()
        self._html = self.image_to_html(self._img)
        self._html_grey = self.image_to_html_grey()


    @property
    def source(self):
        return self._source  
    
    @property
    def label(self):
        return self._label 
    
    @property
    def file(self):
        return self._file   
    
    @property
    def filename(self):
        return self._filename   
    
    @property
    def img(self):
        return self._img
             
    @property
    def format(self):
        return self._format
    
    @property
    def mode(self):
        return self._mode
    
    @property
    def originalSize(self):
        return self._originalSize
    
    @property
    def size(self):
        return self._img.size
    
    @property
    def html(self):
        return self._html
    
    @property
    def html_grey(self):
        return self._html_grey
        
    # Large image scaled to baseWidth of 320
    def scale_image(self):
        scalePercent = (self._baseWidth/float(self._img.size[0]))
        scaleHeight = int((float(self._img.size[1])*float(scalePercent)))
        scale = (self._baseWidth, scaleHeight)
        self._img = self._img.resize(scale)
    
    # PIL image converted to base64
    def image_to_html(self, img):
        with BytesIO() as buffer:
            img.save(buffer, self._format)
            return '<img src="data:image/png;base64,%s">' % base64.b64encode(buffer.getvalue()).decode()
            
    # Create Grey Scale Base64 representation of Image
    def image_to_html_grey(self):
        img_grey = self._img
        numpy = np.array(self._img.getdata()) # PIL image to numpy array
        
        grey_data = [] # key/value for data converted to gray scale
        # 'data' is a list of RGB data, the list is traversed and hex and binary lists are calculated and formatted
        for pixel in numpy:
            # create gray scale of image, ref: https://www.geeksforgeeks.org/convert-a-numpy-array-to-an-image/
            average = (pixel[0] + pixel[1] + pixel[2]) // 3  # average pixel values and use // for integer division
            if len(pixel) > 3:
                grey_data.append((average, average, average, pixel[3])) # PNG format
            else:
                grey_data.append((average, average, average))
            # end for loop for pixels
            
        img_grey.putdata(grey_data)
        return self.image_to_html(img_grey)

        
# prepares a series of images, provides expectation for required contents
def image_data(path=Path("images/"), images=None):  # path of static images is defaulted
    if images is None:  # default image
        images = [
            {'source': "Internet", 'label': "Green Square", 'file': "green-square-16.png"},
            {'source': "Peter Carolin", 'label': "Clouds Impression", 'file': "clouds-impression.png"},
            {'source': "Peter Carolin", 'label': "Lassen Volcano", 'file': "lassen-volcano.jpg"}
        ]
    return path, images

# turns data into objects
def image_objects():        
    id_Objects = []
    path, images = image_data()
    for image in images:
        id_Objects.append(Image_Data(source=image['source'], 
                                  label=image['label'],
                                  file=image['file'],
                                  path=path,
                                  ))
    return id_Objects

# Jupyter Notebook Visualization of Images
if __name__ == "__main__":
    for ido in image_objects(): # ido is an Imaged Data Object
        
        print("---- meta data -----")
        print(ido.label)
        print(ido.source)
        print(ido.file)
        print(ido.format)
        print(ido.mode)
        print("Original size: ", ido.originalSize)
        print("Scaled size: ", ido.size)
        
        print("-- scaled image --")
        display(HTML(ido.html))
        
        print("--- grey image ---")
        display(HTML(ido.html_grey))
        
    print()
---- meta data -----
Green Square
Internet
green-square-16.png
PNG
RGBA
Original size:  (16, 16)
Scaled size:  (320, 320)
-- scaled image --

--- grey image ---

---- meta data -----
Clouds Impression
Peter Carolin
clouds-impression.png
PNG
RGBA
Original size:  (320, 234)
Scaled size:  (320, 234)
-- scaled image --

--- grey image ---

---- meta data -----
Lassen Volcano
Peter Carolin
lassen-volcano.jpg
JPEG
RGB
Original size:  (2792, 2094)
Scaled size:  (320, 240)
-- scaled image --

--- grey image ---

Hacks

  • Add this Blog to you own Blogging site.
  • In the Blog add a Happy Face image.
  • Have Happy Face Image open when Tech Talk starts, running on localhost. Don’t tell anyone. Show to Teacher.
    • smile <!–

      AP Prep

  • In blog add College Board practice problems for 2.2
  • Choose 2 images, one that will more likely result in lossy data compression and one that is more likely to result in lossless data compression. Explain.
    • One image likely to result in lossless data compression is something like a solid color because there is nothing to compress whereas something more complex will be more lossy because it has things that it can compress. –>

Alternative Hack

Something fun with backgrounds. Below is a project from Teacher repository that has some problems. Help the Teacher figure this out.

  1. Identify problems
    1. The image is not scaled properly and is smeared at the end
  2. Propose solutions
    1. Use css to move the image across the screen.
  3. Implement solutions
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }

        .background {
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            /* Use viewport width to make it responsive */
            height: 100vh;
            /* Use viewport height to make it responsive */
            overflow: hidden;
        }

        .background img {
            width: 200%;
            /* Make the image twice as wide as the viewport */
            height: 100%;
            object-fit: cover;
            animation: scrollBackground 10s linear infinite;
        }

        @keyframes scrollBackground {
            0% {
                transform: translateX(0);
            }

            100% {
                transform: translateX(-50%);
                /* Move the image to the left by 50% of its width */
            }
        }

        .content {
            /* Add your content here */
            position: relative;
            z-index: 1;
        }
    </style>
</head>

<body>
    <div class="background">
        <img src="alien_planet.jpg" alt="Background Image">
    </div>
    <div class="content">
        <!-- Your content goes here -->
        <h1>Infinite Background Scroller</h1>
        <p>This is your main content.</p>
    </div>
</body>

</html>

Parallax:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
    <!-- <link rel="stylesheet" href="./style.css"> -->

</head>
<style>
    @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@900&display=swap');

    body,
    html {
        width: 100%;
        height: 100%;
        background: #ddd;
        font-family: 'Montserrat', sans-serif;
        font-size: 99px;
        text-align: center;
    }

    div {
        position: absolute;
    }
</style>

<body>
    <!-- partial:index.partial.html -->
    <!-- This is a recreation of Unfold's (https://dribbble.com/unfold) parallax scene: https://cdn.dribbble.com/users/14268/screenshots/3275340/northface.gif -->
    <div class="scrollDist"></div>
    <div class="main">
        <svg viewBox="0 0 1200 800" xmlns="http://www.w3.org/2000/svg">
            <mask id="m">
                <g class="cloud1">
                    <rect fill="#fff" width="100%" height="801" y="799" />
                    <image xlink:href="https://assets.codepen.io/721952/cloud1Mask.jpg" width="1200" height="800" />
                </g>
            </mask>

            <image class="sky" xlink:href="https://assets.codepen.io/721952/sky.jpg" width="1200" height="590" />
            <image class="mountBg" xlink:href="https://assets.codepen.io/721952/mountBg.png" width="1200"
                height="800" />
            <image class="mountMg" xlink:href="https://assets.codepen.io/721952/mountMg.png" width="1200"
                height="800" />
            <image class="cloud2" xlink:href="https://assets.codepen.io/721952/cloud2.png" width="1200" height="800" />
            <image class="mountFg" xlink:href="https://assets.codepen.io/721952/mountFg.png" width="1200"
                height="800" />
            <image class="cloud1" xlink:href="https://assets.codepen.io/721952/cloud1.png" width="1200" height="800" />
            <image class="cloud3" xlink:href="https://assets.codepen.io/721952/cloud3.png" width="1200" height="800" />
            <text fill="#fff" x="350" y="200">EXPLORE</text>
            <polyline class="arrow" fill="#fff"
                points="599,250 599,289 590,279 590,282 600,292 610,282 610,279 601,289 601,250" />

            <g mask="url(#m)">
                <rect fill="#fff" width="100%" height="100%" />
                <text x="350" y="200" fill="#162a43">FURTHER</text>
            </g>

            <rect id="arrowBtn" width="100" height="100" opacity="0" x="550" y="220" style="cursor:pointer" />
        </svg>
    </div>
    <!-- partial -->
    <script src='https://cdnjs.cloudflare.com/ajax/libs/zepto/1.2.0/zepto.min.js'></script>
    <script src='https://unpkg.co/gsap@3/dist/gsap.min.js'></script>
    <script src='https://unpkg.com/gsap@3/dist/ScrollTrigger.min.js'></script>
    <script src='https://unpkg.com/gsap@3/dist/ScrollToPlugin.min.js'></script>
    <script>
        gsap.set('.main', { position: 'fixed', background: '#fff', width: '100%', maxWidth: '1200px', height: '100%', top: 0, left: '50%', x: '-50%' })
        gsap.set('.scrollDist', { width: '100%', height: '200%' })
        gsap.timeline({ scrollTrigger: { trigger: '.scrollDist', start: 'top top', end: 'bottom bottom', scrub: 1 } })
            .fromTo('.sky', { y: 0 }, { y: -200 }, 0)
            .fromTo('.cloud1', { y: 100 }, { y: -800 }, 0)
            .fromTo('.cloud2', { y: -150 }, { y: -500 }, 0)
            .fromTo('.cloud3', { y: -50 }, { y: -650 }, 0)
            .fromTo('.mountBg', { y: -10 }, { y: -100 }, 0)
            .fromTo('.mountMg', { y: -30 }, { y: -250 }, 0)
            .fromTo('.mountFg', { y: -50 }, { y: -600 }, 0)

        $('#arrowBtn').on('mouseenter', (e) => { gsap.to('.arrow', { y: 10, duration: 0.8, ease: 'back.inOut(3)', overwrite: 'auto' }); })
        $('#arrowBtn').on('mouseleave', (e) => { gsap.to('.arrow', { y: 0, duration: 0.5, ease: 'power3.out', overwrite: 'auto' }); })
        $('#arrowBtn').on('click', (e) => { gsap.to(window, { scrollTo: innerHeight, duration: 1.5, ease: 'power1.inOut' }); }) // scrollTo requires the ScrollTo plugin (not to be confused w/ ScrollTrigger)
    </script>

</body>

</html>