webimage 0.3.1
Webify your images
Requires Python: >=3.14
Keywords: images, web

# 🧙‍♀️ WebImage 🧙‍♂️

A tool to create responsive images for the web.

And Webby Mage makes it all happen under the hood!

## Webify Your Pics

I always seem to have a hard time dealing with images on my blog. The pictures I take on my phone are great and all, but they are really big! And I don't really have a good system for getting them on a post.

It's so bad that sometimes I'll have an entire post about a vacation that has **no pictures** because it's kind of pain to resize those images, find some sort of naming convention, adding them to the correct directory, and then making sure that the HTML is responsive. (Are we even using `srcset`?)

This package hopes to ease the burden just a little bit by automating some of that process. Dump your images in a directory. Then use this package to automatically resize and optimize your images for the web, also optionally generating an HTML snippet you can drop in your code.

## Getting Started

Keep in mind, `WebImage` is a work in progress.

However, you are welcome to check out the contribution guide if you wish to help!

### From PyPI

You can install `WebImage` as a dependency.

```sh
# With pip
python -m pip install webimage

# or uv
uv add webimage
```

### Install Using git

You can work on, contribute to, or use `WebImage` by using git:

```sh
git clone https://git@codeburg.com:pythonbynight/webimage
cd webimage
```

#### Install Dependencies

I recommend using `uv` for dependency management. You can use other package managers that allow you to install from `pyproject.toml`, but your mileage may vary.

If you don't have `uv` installed, you can use a curl command:

```sh
curl -LsSf https://astral.sh/uv/install.sh | sh
```

If you already have it installed, you may want to make sure that you are using the latest version.

```sh
uv self update
```

Once `uv` is installed or updated, you can load the project dependencies.

```sh
uv sync
```

This will create a virtual environment and install all project dependencies in there.

> Note: Currently, the only two dependencies are `attrs` and `pillow`


## Usage

You can use `WebImage` as any other Python package.


```python
from webimage import WebImage

src_path: Path = path/to/img.jpg

webimg = WebImage(src_path=src_path)

# Creates default set (3 total) of responsive images
webimg.render()

# Creates a single image based predefined options of "small" (350px width), "medium" (600px width), "large" (1200 px width), and "thumbnail" (150px width)
webimg.render(size=small)
```

These are just two methods, with more options available. But first, let's look at what we're trying to achieve.

## Output

WebImage comes configured with some sensible defaults to create a set of images for responsive websites.

The goal is to take an image and create several copies at different scale/sizes, which will then be selected by the browser based on certain conditions.

The target html will follow this pattern.

```html
<img
  srcset=""
  src=""
  sizes=""
  alt=""
/>
```

- `srcset` contains possible image sources for the browser's User Agent to use.
- `src` is the image URL that serves as a fallback.
- `sizes` represent one or more values separated by commas, which helps a browser choose which image to render.
- `alt` is the textual replacement for the image (value not rendered by `WebImage`).

### sizes

The trickiest of these is generally the `sizes` element, because it can be dependent on a user's specific CSS layout. The `sizes` attribute is meant to provide a set of media conditions (screen widths) to indicate which image defined in `srcset` should be displayed.

`WebImage` defaults* the `sizes` attribute as follows:

```html
<img
  ...
  sizes="(max-width: 600px) 96vw, 800px>"
  ...
/>
```

What this snippet means is that if the viewport width is less than 600px (usually a tablet or mobile), then the image should use 96vw width (which is nearly 100% of the viewport width). Otherwise, if the viewport is wider than 600px, the image should be 800px wide.

These values can be configured/modified when defining the `WebImage` object.

> _*defaults_: This benefits a layout with a "content" block that is approximately 800px wide. If the style of your website demands a wider "content" area, or if your site has two or three columns (and different media queries), this default may not work.

### srcset

The images generated by `WebImage` should be referenced in the `srcset` attribute. It should contain a path to the image, the image name, and the actual width (w) in pixels. By default, `WebImage` will produce three images:

```html
<img
  srcset="
    image_200w.webp 200w,
    image_500w.webp 500w,
    image_800w.webp 800w
  "
  ...
/>
```

By default, the images generated will be `*.webp` format, but this can be configured for `jpg` or `png` formats through the `WebImage` object.

Optionally, `srscet` items can be generated through a `SrcsetItem` property, which will also allow you to prepend the image name with a specific path (i.e., to instead render something like `/static/img/image_200w.webp 200w`)

### src

Lastly, the `src` attribute serves as the default image the browser will try to load. This is generally for older browsers that don't support the `srcset` attribute. For our purposes, the default will be the "largest" of the images generated.


## Example

Like mentioned above, there are several configuration elements to help render the images you need. `WebImage` will also optionally create a file with the `html` markup that corresponds to each image.

```python
from webimage import WebImage

src_path = "path/to/image.jpg"

webimg = WebImage(
    src_path=src_path,
    destination_path="/static/images",
    max_width=1000, 
    img_format="png",
    html_output=True
)

webimg.render()
```

The `src_path` is the only required attribute and self explanatory (the location of the file to be resized).

By default, the resulting images would be created in the same directory of the source image. However, you may prefer to have these rendered elsewhere. That is what `destination_path` is for. Above, the rendered images also go into a static images directory.

The above code will render three images in the `/static/images` directory and a `txt` file that contains the html snippet for these images (be aware that the rendered file will be in the `destination_path`). In `image.txt`, you will find the following snippet:

```html
<img
  srcset=
  "
    image_200w.png 200w,
    image_600w.png 600w,
    image_1000w.png 1000w
  "
  src="image_1000w.png"
  sizes="(max-width: 1000px) 96vw, 1000px>"
  alt="..."
/>
<!-- Remember to add alt text -->
 ```


## Details

When creating your `WebImage` object, you can include any of the following attributes.

- `source_path`: The path to the source image.
- `destination_path`: The path to the destination directory where the rendered images will be saved.
- `max_width`: The maximum width of the rendered images in pixels.
- `min_width`: The minimum width of the rendered images in pixels.
- `total_images`: The total number of images to be rendered.
- `img_format`: The format of the rendered images (e.g., "jpg", "png").
- `quality`: The quality of the rendered images (1-100).
- `html_output`: Whether to generate an HTML snippet for the rendered images.
- `html_srcset_path`: The string that will be concatenated in front of the image name in the `srcset` attribute.

This allows you to control how `WebImage` generates images.

For example, setting a `min_width` and `max_width` will determine the widths of the smallest and largest images referenced in the `srcset` attribute.

By default, `WebImage` will generate 3 images, but this can be any value from 1 to 5.

The `img_format` determines the output image format, while the `quality` is used by `PIL`. The lower the quality, the lower the filesize.

Lastly, the `html_output` controls whether a `txt`, `md`, or `html` file is created with the html snippet appropriate for a set of images. (Configurable through an `HTMLWriter` class.)

### Webi ... Mage?
The transformation of the images is handled by another class called `Mage`. Get it? Mage? Because the images are transformed? 🧙 Hillarious, right?

When you call `render` on the `WebImage` class, it uses `Mage` under the hood to do the transformations.

But you can also access the mage class directly:

```python
from webimage import WebImage

src_path: Path = path/to/img.jpg

webimg = WebImage(src_path=src_path)

# Inspect the properties of the image
webimg.mage.inspect_image()
```

This will return an object with the `filename`, `size`, `img_format`, and `mode` of the image file.

```python
webimg.mage.transform(size=(200, 200), operation="contain")
```

This will transfrom the image to a size of 200x200 pixels, and it will resize the image with max width/height within the 200x200 size, maintaining aspect ratio and cropping if necessary.

The other `operations` supported (from `PIL`) are as follows:

- `scale`: Resizes image given a specific ratio (target width / source width).
- `contain`: Resizes image with max width/height within given size, maintaining aspect ratio.
- `cover`: Resizes image to fill the given size while maintaining aspect ratio, cropping if necessary.
- `fit`: Resized and cropped version, based on given size.
- `pad`: Resizes and pads the image, expanded to fill the requested aspect ratio and size.

## Other Stuff

As refrenced earlier, an `HTMLWriter` handles the output to HTML.

This is still a work in progress, but for now, it supports adding a CSS class to the `srcset` output, by passing arguments on the `render` command.

```python
from webimage import WebImage

src_path: Path = path/to/img.jpg

webimg = WebImage(max_width=800, src_path=src_path)

webimg.render(css_class="webimage")
```

This will produce something like this:

```html
<img class="webimage"
  srcset=
  "
    image_200w.png 200w,
    image_600w.png 600w,
    image_800w.png 800w
  "
  src="image_800w.png"
  sizes="(max-width: 800px) 96vw, 800px"
  alt="..."
/>
```

## What Next?
This is still a bit rough around the edges.

The fact is, I have been looking for ways to make it easier to blog about stuff and add some cool pictures, but dealing with them always feels like a chore.

If I could automate some of that work of resizing and generating responsive markup, it may encourage me to include more pics!

But at the same time, I would like for this library to be flexible and stable enough that others could use it as well.

I'll be working on ironing out some of those rough edges and adding any features that seem useful.

If you're up to it...

## Contributing
Well, you made it down this far, and that's saying something!

If you think you can help in any way, shape, or form, look at the contribution guide for details.

Thanks!