Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for webp format #6035

Merged
merged 9 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions examples/reference/panes/Image.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"outputs": [],
"source": [
"jpg_pane = pn.pane.Image('https://assets.holoviz.org/panel/samples/jpeg_sample.jpeg')\n",
"png_pane = pn.pane.Image('https://assets.holoviz.org/panel/samples/png_sample.png')"
"png_pane = pn.pane.Image('https://assets.holoviz.org/panel/samples/png_sample.png')\n",
"webp_pane = pn.pane.Image('https://assets.holoviz.org/panel/samples/webp_sample.webp')"
]
},
{
Expand All @@ -58,7 +59,7 @@
},
"outputs": [],
"source": [
"pn.Column(jpg_pane, png_pane)"
"pn.Column(jpg_pane, png_pane, webp_pane)"
]
},
{
Expand Down
102 changes: 102 additions & 0 deletions examples/reference/panes/WebP.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import panel as pn\n",
"\n",
"pn.extension()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `WebP` pane embeds a `.web` image file in a panel if provided a local path, or will link to a remote image if provided a URL.\n",
"\n",
"#### Parameters:\n",
"\n",
"For details on other options for customizing the component see the [layout](../../how_to/layout/index.md) and [styling](../../how_to/styling/index.md) how-to guides.\n",
"\n",
"* **``alt_text``** (str, default=None): alt text to add to the image tag. The alt text is shown when a user cannot load or display the image. \n",
"* **``embed``** (boolean, default=False): If given a URL to an image this determines whether the image will be embedded as base64 or merely linked to.\n",
"* **``fixed_aspect``** (boolean, default=True): Whether the aspect ratio of the image should be forced to be equal.\n",
"* **``link_url``** (str, default=None): A link URL to make the image clickable and link to some other website.\n",
"* **``object``** (str or object): The PNG file to display. Can be a string pointing to a local or remote file, or an object with a ``_repr_png_`` method.\n",
"* **``style``** (dict): Dictionary specifying CSS styles\n",
"\n",
"___"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `WebP` pane can be pointed at any local or remote `.webp` file. If given a URL starting with `http` or `https`, the `embed` parameter determines whether the image will be embedded or linked to:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"webp_pane = pn.pane.WebP('https://assets.holoviz.org/panel/samples/webp_sample.webp')\n",
"\n",
"webp_pane"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can scale the size of the image by setting a specific fixed `width` or `height`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"webp_pane.clone(width=400)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Alternatively we can scale the width and height using the `sizing_mode`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pn.pane.WebP(\n",
" 'https://assets.holoviz.org/panel/samples/webp_sample2.webp', sizing_mode='scale_width'\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that by default the aspect ratio of the image is fixed, and so there may be a gap beside or below the image even in responsive sizing modes. To override this behavior set `fixed_aspect=False` or provide fixed `width` and `height` values."
]
}
],
"metadata": {
"language_info": {
"name": "python",
"pygments_lexer": "ipython3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
2 changes: 1 addition & 1 deletion panel/pane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from .equation import LaTeX # noqa
from .holoviews import HoloViews, Interactive # noqa
from .image import ( # noqa
GIF, ICO, JPG, PDF, PNG, SVG, Image,
GIF, ICO, JPG, PDF, PNG, SVG, Image, WebP,
)
from .ipywidget import IPyLeaflet, IPyWidget, Reacton # noqa
from .markup import ( # noqa
Expand Down
48 changes: 48 additions & 0 deletions panel/pane/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import asyncio
import base64
import struct

from io import BytesIO
from pathlib import PurePath
Expand Down Expand Up @@ -496,3 +497,50 @@ def _transform_object(self, obj: Any) -> Dict[str, Any]:
page = f'#page={self.start_page}' if getattr(self, 'start_page', None) else ''
html = f'<embed src="{obj}{page}" width={w!r} height={h!r} type="application/pdf">'
return dict(text=escape(html))

class WebP(ImageBase):
"""
The `WebP` pane embeds a .webp image file in a panel if
provided a local path, or will link to a remote image if provided
a URL.

Reference: https://developers.google.com/speed/webp/docs/riff_container

:Example:

>>> WebP(
... 'https://assets.holoviz.org/panel/samples/webp_sample.webp',
... alt_text='A nice tree',
... link_url='https://en.wikipedia.org/wiki/WebP',
... width=500,
... caption='A nice tree'
... )
"""

filetype: ClassVar[str] = 'webp'

_extensions: ClassVar[Tuple[str, ...]] = ('webp',)

@classmethod
def _imgshape(cls, data):
with BytesIO(data) as b:
b.read(12) # Skip RIFF header
chunk_header = b.read(4).decode('utf-8')
if chunk_header[:3] != 'VP8':
raise ValueError("Invalid WebP file")
wptype = chunk_header[3]
b.read(4)
if wptype == 'X':
b.read(4)
w = int.from_bytes(b.read(3), 'little') + 1
h = int.from_bytes(b.read(3), 'little') + 1
elif wptype == 'L':
b.read(1)
bits = struct.unpack("<I", b.read(4))[0]
w = (bits & 0x3FFF) + 1
h = ((bits >> 14) & 0x3FFF) + 1
elif wptype == ' ':
b.read(6)
w = int.from_bytes(b.read(2), 'little') + 1
h = int.from_bytes(b.read(2), 'little') + 1
return int(w), int(h)
19 changes: 11 additions & 8 deletions panel/tests/pane/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
from requests.exceptions import MissingSchema

from panel.pane import (
GIF, ICO, JPG, PDF, PNG, SVG,
GIF, ICO, JPG, PDF, PNG, SVG, WebP,
)
from panel.pane.markup import escape

JPG_FILE = 'https://assets.holoviz.org/panel/samples/jpg_sample.jpg'
JPEG_FILE = 'https://assets.holoviz.org/panel/samples/jpeg_sample.jpeg'
PNG_FILE = 'https://assets.holoviz.org/panel/samples/png_sample.png'
SVG_FILE = 'https://assets.holoviz.org/panel/samples/svg_sample.svg'

WEBP_FILE = 'https://assets.holoviz.org/panel/samples/webp_sample.webp'

def test_jpeg_applies():
assert JPG.applies(JPEG_FILE)
Expand Down Expand Up @@ -69,14 +69,17 @@ def test_svg_pane(document, comm):
b'AAAAAAAAAAAAAAAAFBv/EABkRAAEFAAAAAAAAAAAAAAAAAAEAAjFxsf/aAAwDAQ' + \
b'ACEQMRAD8AA0qs5HvTHQcJdsChioXSbOr/2Q==',
ico = b'AAABAAEAAgEAAAEAIAA0AAAAFgAAACgAAAACAAAAAgAAAAEAIAAAAAAACAAAAHQ' + \
b'SAAB0EgAAAAAAAAAAAAD//////////wAAAAA=')
b'SAAB0EgAAAAAAAAAAAAD//////////wAAAAA=',
webp= b'UklGRkIAAABXRUJQVlA4WAoAAAAQAAAAAQAAAAAAQUxQSAMAAAAAAAAAVlA4IBg' + \
b'AAAAwAQCdASoCAAEAAUAmJaQAA3AA/v02aAA='
)


def test_imgshape():
for t in [PNG, JPG, GIF, ICO]:
w,h = t._imgshape(b64decode(twopixel[t.name.lower()]))
assert w == 2
assert h == 1
@pytest.mark.parametrize('t', [PNG, JPG, GIF, ICO, WebP], ids=lambda t: t.name.lower())
def test_imgshape(t):
w, h = t._imgshape(b64decode(twopixel[t.name.lower()]))
assert w == 2
assert h == 1

def test_load_from_byteio():
"""Testing a loading a image from a ByteIo"""
Expand Down
Loading