UP | HOME

📷 Photography

On this page I want to present you some photos I took.

Photo Editor Class

The idea is to edit photos automatically in the build pipeline, so I do not have to do this manually. This class provides methods to automatically edit photos and can be called by another script or code block.

The following Python code defines two classes, Photo and PhotoEditor, that are used to edit and scale down photos in a given directory. The Photo class scales down photos and saves the new, smaller version of the photo to a new file with the suffix “_small” added to the original filename stem. The PhotoEditor class finds all the .jpg, .jpeg, and .JPG files in the working directory, creates a Photo object for each file, and returns a list of Photo objects representing all the photos in the directory.

from pathlib import Path
from PIL import Image, ExifTags


class Photo:
    """
    Represents an image file and provides methods for scaling it down.

    Attributes:
        file (Path): The file path of the image.
        small_file (Path): The file path of the scaled-down image.
        image (PIL.Image): The PIL Image object for the image.
        exif (dict): The exif data for the image.

    Properties:
        size: Returns the size of the image as a tuple of (width, height).
        maker: Returns the maker of the camera that took the image.
        camera: Returns the model of the camera that took the image.
        date: Returns the date and time when the image was taken.

    Methods:
        calc_ratio: Calculates the width and height ratio to scale the image down.
        scale_down: Scales the image down and saves it to the small_file path.
    """

    def __init__(self, file: Path):
        """
        Initializes a Photo object with a file path.

        Args:
            file (Path): The file path of the image.
        """
        self.file = file
        self.small_file = self.file.with_stem(f"{self.file.stem}_small")

        try:
            self.image = Image.open(self.file)
        except OSError:
            print("cannot open", self.file)

        self.exif = self.image.getexif()
        self.exif_data = {ExifTags.TAGS[k]: v for k,v in self.exif.items()}

    @property
    def size(self):
        """
        Returns the size of the image as a tuple of (width, height).

        Returns:
            A tuple of (width, height) for the size of the image.
        """
        return self.image.size

    @property
    def maker(self):
        """
        Returns the maker of the camera that took the image.

        Raises:
            KeyError: If the maker information is not found in the exif data.

        Returns:
            The maker of the camera as a string.
        """
        try:
            return self.exif[271]
        except KeyError:
            print("Could not find maker in exif data", self.file)

    @property
    def camera(self):
        """
        Returns the model of the camera that took the image.

        Raises:
            KeyError: If the camera information is not found in the exif data.

        Returns:
            The model of the camera as a string.
        """
        try:
            return self.exif[272]
        except KeyError:
            print("Could not find camera in exif data", self.file)

    @property
    def date(self):
        """
        Returns the date and time when the image was taken.

        Raises:
            KeyError: If the date information is not found in the exif data.

        Returns:
            The date and time when the image was taken as a string.
        """
        try:
            return self.exif[306]
        except KeyError:
            print("Could not find date in exif data", self.file)

    @staticmethod
    def calc_ratio(max_width: int, size: tuple) -> tuple:
        """
        Calculates the width and height ratio to scale the image down.

        Args:
            max_width (int): The maximum width of the scaled-down image.
            size (tuple): The original size of the image as a (width, height) tuple.

        Returns:
            A tuple of (width, height) to scale the image down.
        """
        return (max_width, int((size[1] / 100) * ((max_width / size[0]) * 100)))

    def scale_down(self) -> Path:
        """
        Scales the image down and saves it to the small_file path.

        Raises:
            OSError: If the file cannot be saved.

        Returns:
            The file path of the scaled-down image.
        """
        im = self.image.resize(self.calc_ratio(992, self.image.size))
        try:
            im.save(self.small_file)
            return self.small_file
        except OSError:
            print("cannot save", self.file)


class PhotoEditor:
    """
    Provides methods for editing multiple photos.

    Attributes:
        wd (Path): The working directory containing the photos.

    Methods:
        photos: Returns a list of Photo objects for all the photos in the working directory.
        scaledown_photos: Returns a list of file paths for scaled-down versions of the photos.
    """

    def __init__(self, wd):
        """
        Initializes a PhotoEditor object with a working directory.

        Args:
            wd (Path): The working directory containing the photos.
        """
        self.wd: Path = wd

    @property
    def photos(self) -> list:
        """
        Returns a list of Photo objects for all the photos in the working directory.

        Returns:
            A list of Photo objects.
        """
        files = []
        for suffix in ["*.jpg", "*.jpeg", "*.JPG"]:
            files += self.wd.glob(suffix)
        photos = []
        for file in files:
            p = Photo(file)
            if "_small" not in p.file.stem and p.date:
                photos.append(p)
        return sorted(photos, key=lambda photo: photo.date)

    def scaledown_photos(self, _photos) -> list:
        """
        Returns a list of file paths for scaled-down versions of the photos.

        Args:
            _photos (list): A list of Photo objects.

        Returns:
            A list of file paths for the scaled-down images.
        """
        scaled_photos = []
        for photo in _photos:
            if "_small" not in photo.file.stem:
                scaled_photos.append([photo.scale_down(),
                                      photo.size,
                                      photo.maker,
                                      photo.camera,
                                      photo.date])
        return scaled_photos

The following Python code uses a function called generate_html to create an HTML unordered list of photo file names from a given list of file names. It then creates an instance of the PhotoEditor class with a Path object representing the working directory and calls the scaledown_photos method of the PhotoEditor class to obtain a list of scaled-down photo file names. This list is then sorted and passed as an argument to the generate_html function to generate the final HTML list of photo file names, which is printed to the console.

from pathlib import Path
from photoeditor import PhotoEditor


def generate_html(items: list) -> str:
    html_list = '<ul class="photo-collection">'
    for photo in items:
        html_img = '<li class="item">'
        html_img += f'<img class="photography photo" src="./{photo[0]}"/>'
        html_img += f'<ul class="photo-property"><li>{photo[1]}</li><li>{photo[2]}</li><li>{photo[3]}</li><li>{photo[4]}</li></ul>'
        html_img += '</li>'
        html_list += html_img
    html_list += "</ul>"
    return html_list


pe = PhotoEditor(Path(wd))
print(generate_html(pe.scaledown_photos(pe.photos)))

    Zoom-in and Zoom-out

    To be able to see more details of high resolution photos, I developed a tiny DOM-Script to be able to zoom-in into the photos on this page:

    /**
     * Creates and returns a new zoomContainer div and zoomImg img DOM elements.
     *
     * @returns {Object} An object containing the newly created zoomContainer and zoomImg elements.
     */
    function createZoomElements() {
        let zoomContainer = document.createElement("div");
        let zoomImg = document.createElement("img");
        zoomImg.className = "zoom-img";
        zoomContainer.id = "zoom-container";
    
        return { zoomContainer, zoomImg };
    }
    
    /**
     * Sets up a click event listener on a zoomContainer element to hide the
     * zoomContainer and clear the zoomImg source.
     *
     * @param {HTMLElement} zoomContainer - The zoomContainer div to add the event listener to.
     * @param {HTMLElement} zoomImg - The zoomImg img whose source will be cleared when the zoomContainer is clicked.
     */
    function setZoomContainerListener(zoomContainer, zoomImg) {
        zoomContainer.addEventListener("click", () => {
            zoomContainer.style.display = "none";
            zoomImg.src = "";
        });
    }
    
    /**
     * Sets up click event listeners on all elements with the class "photo" to
     * toggle the display of the zoomContainer and set the zoomImg source to the
     * clicked photo's source.
     *
     * @param {HTMLElement} zoomContainer - The zoomContainer div to toggle the display of.
     * @param {HTMLElement} zoomImg - The zoomImg img to set the source of.
     */
    function setPhotosListener(zoomContainer, zoomImg) {
        document.querySelectorAll(".photo").forEach((photo) => {
            photo.addEventListener("click", () => {
                if (zoomContainer.style.display === "flex") {
                    zoomContainer.style.display = "none";
                } else {
                    zoomImg.src = photo.src.replace("_small", "");
                    zoomContainer.style.display = "flex";
    
                    if (!zoomContainer.querySelector(".zoom-img")) {
                        zoomContainer.appendChild(zoomImg);
                    }
    
                    document.body.appendChild(zoomContainer);
                }
            });
        });
    }
    
    /**
     * On window load, creates zoomContainer and zoomImg elements, sets up a click
     * event listener on the zoomContainer, and sets up click event listeners on
     * all photos to toggle the zoom functionality.
     */
    window.onload = () => {
        const { zoomContainer, zoomImg } = createZoomElements();
        setZoomContainerListener(zoomContainer, zoomImg);
        setPhotosListener(zoomContainer, zoomImg);
    };
    

    This JavaScript code creates a zoomable image feature for a photo gallery on a web page. It sets up an event listener for when the page loads and creates a new container element for the zoomed image and a new image element for the zoomed-in photo. It adds event listeners to each of the thumbnail images in the gallery, and when a thumbnail image is clicked, it displays the zoom container with the zoomed image, and hides the container when the user clicks on it or outside of it. This code allows users to click on a thumbnail image in a photo gallery to zoom in on the photo and return to the gallery by clicking outside the zoom container.

    Nuremberg

    Weltenacker Westpark

      • (3952, 1824)
      • Fairphone
      • FP4
      • 2022:08:13 14:42:41
      • (3952, 1824)
      • Fairphone
      • FP4
      • 2022:09:05 19:09:20
      • (3952, 1824)
      • Fairphone
      • FP4
      • 2022:09:30 16:13:01
      • (3952, 1824)
      • Fairphone
      • FP4
      • 2022:10:09 16:44:53
      • (3952, 1824)
      • Fairphone
      • FP4
      • 2022:10:29 16:11:45
      • (3952, 1824)
      • Fairphone
      • FP4
      • 2022:11:10 14:26:20
      • (3952, 1824)
      • Fairphone
      • FP4
      • 2023:04:03 13:35:58
      • (3952, 1824)
      • Fairphone
      • FP4
      • 2023:04:10 13:50:54
      • (3952, 1824)
      • Fairphone
      • FP4
      • 2023:04:29 13:19:05
      • (3952, 1824)
      • Fairphone
      • FP4
      • 2023:06:09 15:18:21
      • (3952, 1824)
      • Fairphone
      • FP4
      • 2023:06:17 18:27:26

    Old Town

      • (6000, 4000)
      • SONY
      • ILCE-6300
      • 2019:10:20 16:15:12
      • (6000, 4000)
      • SONY
      • ILCE-6300
      • 2022:12:23 14:36:48

    Istanbul

    Author: Marcus Kammer

    Email: marcus.kammer@mailbox.org

    Date: Sat, 18 May 2024 15:06 +0200

    Emacs 29.1.90 (Org mode 9.6.11)

    License: CC BY-SA 3.0