The Breakdown

This program expands on the current model that we went over in class. I've retained a similar class structures, with minor edits to one method for blue scaling and added a new method to handle brightening the images.

Bluescaling

This methods expands on the original greyscaling (black and white) method. However, instead of calculating an average, I just set all other color channels to a value of 0, and maintain the original coloration of the blue channel.

Original:

average = (pixel[0] + pixel[1] + pixel[2]) // 3
...
image['gray_data'].append((average, average, average, pixel[3]))

Modified:

blue_data.append((0, 0, pixel[2], pixel[3]))

Brightness

This method was also simple to create as we've already established a baseline for converting an image into an np array of pixel rgb values. All I needed to do to increase the brightness was to take in a percentage from a user, and multiply each pixel value by said ratio to increase the overall brightness by the user specified values.

One thing that we did need to be careful of, was to take into account that the multipled value may be greater than 255, which is the maximum accepeted value for any RGB value. To do this, we would use the min() method to perform selection

# Create Brightened Base64 representation of Image
    def image_to_html_brightened(self):
        img_brightened = self._img
        numpy = np.array(self._img.getdata()) # PIL image to numpy array
        ratio = 1+int(input("How much do you want to scale the brightness by? (0-50%)"))/100 # Convert user inputted number into a percentage, then add that onto 1 to get our multiplier
        assert ratio < 1.5
        brightened_data = []
        for pixel in numpy:
            # Now we try to apply the multiplier to each color channel, we use the min() method here to avoid values greater than 255, the upper limit
            try:
                brightened_data.append((min(int(round(pixel[0]*ratio)), 255), min(int(round(pixel[1]*ratio)), 255), min(int(round(pixel[2]*ratio)), 255), pixel[3])) # PNG format
            except:
                brightened_data.append((min(int(round(pixel[0]*ratio)), 255), min(int(round(pixel[1]*ratio)), 255), min(int(round(pixel[2]*ratio)), 255)))
            # end for loop for pixels

        print("Image scaled by {0}%".format((ratio-1)*100))
        img_brightened.putdata(brightened_data)
        return self.image_to_html(img_brightened)
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 PIL import ImageOps
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_brightened = self.image_to_html_brightened()
        self._html_blue = self.image_to_html_blue()
        


    @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_blue(self):
        return self._html_blue
    
    @property
    def html_red(self):
        return self._html_red
    
    @property
    def html_green(self):
        return self._html_green
    
    @property
    def html_brightened(self):
        return self._html_brightened
    
    # 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 Brightened Base64 representation of Image
    def image_to_html_brightened(self):
        img_brightened = self._img
        numpy = np.array(self._img.getdata()) # PIL image to numpy array
        ratio = 1+int(input("How much do you want to scale the brightness by? (0-50%)"))/100 # Convert user inputted number into a percentage, then add that onto 1 to get our multiplier
        assert ratio < 1.5
        brightened_data = []
        for pixel in numpy:
            # Now we try to apply the multiplier to each color channel, we use the min() method here to avoid values greater than 255, the upper limit
            try:
                brightened_data.append((min(int(round(pixel[0]*ratio)), 255), min(int(round(pixel[1]*ratio)), 255), min(int(round(pixel[2]*ratio)), 255), pixel[3])) # PNG format
            except:
                brightened_data.append((min(int(round(pixel[0]*ratio)), 255), min(int(round(pixel[1]*ratio)), 255), min(int(round(pixel[2]*ratio)), 255)))
            # end for loop for pixels
            
        print("Image scaled by {0}%".format((ratio-1)*100))
        img_brightened.putdata(brightened_data)
        return self.image_to_html(img_brightened)
    
    # Create Grey Scale Base64 representation of Image
    def image_to_html_blue(self):
        img_blue = self._img
        numpy = np.array(self._img.getdata()) # PIL image to numpy array
        blue_data = []
        for pixel in numpy:
            # Same syntax as before, but we set all color channels to 0
            try:
                blue_data.append((0, 0, pixel[2], pixel[3])) # PNG format
            except:
                blue_data.append((0, 0, pixel[2]))
            # end for loop for pixels
            
        img_blue.putdata(blue_data)
        return self.image_to_html(img_blue)
    
    def mirror(self):
        img = np.fliplr(np.array(self._img)) # PIL image to numpy array
        print(img)
        img_mirror = pilImage.fromarray(img)
        return self.image_to_html(img_mirror)

        
# 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("--- red image ---")
        # display(HTML(ido.html_red))
        
        print("--- blue image ---")
        display(HTML(ido.html_blue))

        print("--- brightened image ---")
        display(HTML(ido.html_brightened))

        # print("--- green image ---")
        # display(HTML(ido.html_green))

        
        
    print()
Image scaled by 42.99999999999999%
Image scaled by 33.00000000000001%
Image scaled by 28.000000000000004%
---- meta data -----
Green Square
Internet
green-square-16.png
PNG
RGBA
Original size:  (16, 16)
Scaled size:  (320, 320)
-- scaled image --
--- blue image ---
--- brightened image ---
---- meta data -----
Clouds Impression
Peter Carolin
clouds-impression.png
PNG
RGBA
Original size:  (320, 234)
Scaled size:  (320, 234)
-- scaled image --
--- blue image ---
--- brightened image ---
---- meta data -----
Lassen Volcano
Peter Carolin
lassen-volcano.jpg
JPEG
RGB
Original size:  (2792, 2094)
Scaled size:  (320, 240)
-- scaled image --
--- blue image ---
--- brightened image ---

Lossy vs Lossless

Lossy Compression: A form of compression where data is usually lost or combined with other repeatitive patterns, resulting in an endproduct that is irreversible to produce the original item.

Lossless Compression: Opposite of Lossy compression. This form for compression could compress data with greater carefullness and present a end-product that could be scaled back up to the original item. However, this typically sees performance issues as efficiency is reduced compared to lossy compression

Example

Lossless Lossy

In the examples above, although the two images may look the same, the first image is lossless because of how it utilizes the SVG (Scalable Vector Graphics) format, where the image is generated by vector graphics (representation of an image through lines, curves an dots). As such, we can scale this image however we want, and our computer or browser will always render it without any loss of data, as the mathematical operations used to render the image remains the same

However, things change for the second image. The second image is a png file of a screenshot of the first image that I took in my browser. Because this image is stored in PNG (Portable Network Graphics) format, whenever we scale this image, we are bound to lose data as some pixels will merge in one way or another with others, resulting in lower graphics if we apply size compression on this image.