defmodule Image.Social do
@moduledoc """
Functions to introspect standard image sizes for
various social media platforms.
"""
alias Vix.Vips.Image, as: Vimage
@typedoc """
The social media platform image usage.
"""
@type image_usage :: atom()
@typedoc """
The known social media platforms.
"""
@type platform ::
:facebook
| :twitter
| :linkedin
| :pinterest
| :instagram
| :tumblr
| :youtube
| :snapchat
| :tiktok
# Before Version 1.0, this content will move from being a static
# list to a dynamic one that can be updated at runtime
# Facebook: https://www.facebook.com/help/125379114252045?helpref=faq_content
# https://www.socialpilot.co/blog/social-media-image-sizes
# https://blog.hootsuite.com/social-media-image-sizes-guide/
@social_sizes %{
facebook: %{
default: :post,
profile: "170x170",
cover_desktop: "820x312",
cover_mobile: "640x360",
post: "1200x630",
banner: "1200x630",
story: "1080x1920"
},
twitter: %{
profile: "400x400",
cover: "1500x500",
shared: "900x450",
stream: "440x220"
},
linkedin: %{
profile: "400x400",
cover: "1584x396",
company_logo: "400x400",
blog: "1200x627"
},
pinterest: %{
profile: "165x165",
cover: "800x450",
pin: "1080x1920",
board: "222x150"
},
instagram: %{
profile: "320x320",
cover: "1080x1920",
photo: %{
landscape: "1080x596",
portrait: "1080x1350",
square: "1080x1080"
},
thumb: "161x161",
story: "1080x1920",
carousel: %{
landscape: "1080x566",
portrait: "1080x1350",
square: "1080x1080"
}
},
tumblr: %{
profile: "128x128",
banner: "3000x1055",
post: "500x750"
},
youtube: %{
cover: "2560x1440",
profile: "800x800",
banner: "2048x1152",
thumbnail: "1280x720"
},
snapchat: %{
photo: "1080x1920",
share: "750x1334"
},
tiktok: %{
profile: "20x20",
video: "1080x1920"
}
}
@social_platforms Map.keys(@social_sizes)
@doc """
Return the map of social media image
sizes.
The returned map of maps is of a standard form:
* The first key is the platform name (ie `:twitter`)
* The second key is the image type for the platform (ie `:profile`)
* The third level is optional. But if it exists it must
have three keys: `:landscape`, `:portrait` and `:square`
The values are all of the form "WxH" where `W` is the
width in pixels and `H` is the height in pixels.
"""
def media_sizes do
@social_sizes
end
@doc """
Returns a list of known social
platforms.
### Example
"""
@spec known_platforms :: [platform()]
def known_platforms do
@social_platforms
end
@doc """
Returns the image types for the given
social platform.
### Arguments
* `platform` is any known social platform.
See `Image.Social.known_platforms/0`.
### Returns
* A list of images uses available for the
platform.
### Example
"""
@spec image_usages(platform()) :: [image_usage()]
def image_usages(platform) when platform in @social_platforms do
media_sizes()
|> Map.fetch!(platform)
|> Map.delete(:default)
|> Map.keys()
end
@doc """
Returns the default image type that an
image is resized to for a given platform
is no `:type` parameter is provided.
### Arguments
* `platform` is any known social platform.
See `Image.Social.known_platforms/0`.
### Returns
* The default image type for the platform.
### Example
"""
@spec default_image_usage(platform()) :: image_usage()
def default_image_usage(platform) when platform in @social_platforms do
media_sizes()
|> Map.fetch!(platform)
|> Map.get(:default)
end
@doc """
Resize an image for a particular social
platform and usage.
This function:
* Resizes an image to the correct dimensions, including being
image aspect aware
* Converts to the sRGB color space
* Minimises metadata (retains only Artist and Copyright)
### Arguments
* `image` is any `t:Vix.Vips.Image.t/0`
* `platform` is the name of a known social
media platform. See `Image.Social.known_platforms/0`.
* `options` is a keyword list of options.
### Options
* `:type` is the image type within the social
platform for which the image should be resized. See
`Image.Social.image_usages/1`
* All other options are passed to `Image.thumbnail!/3`.
### Returns
* `{:ok, resized_image}` or
* `{:error, reason}`
"""
@spec resize(Vimage.t(), platform(), Keyword.t()) ::
{:ok, Vimage.t()} | {:error, Image.error_message()}
def resize(image, platform, options \\ [])
def resize(%Vimage{} = image, platform, options) when platform in @social_platforms do
{usage, options} = Keyword.pop(options, :usage, :default)
options = Keyword.put_new(options, :crop, :attention)
aspect = Image.aspect(image)
platform = Map.fetch!(media_sizes(), platform)
with {:ok, size} <- get_image_size(platform, usage, aspect) do
image
|> Image.thumbnail!(size, options)
|> Image.to_colorspace!(:srgb)
|> Image.minimize_metadata()
end
end
def resize(%Vimage{} = _image, platform, _options) do
{:error, unknown_platform_error(platform)}
end
@doc """
Resize an image for a particular social
platform and usage.
This function:
* Resizes an image to the correct dimensions, including being
image orientation aware
* Converts to the sRGB color space
* Minimises metadata (retains only Artist and Copyright)
### Arguments
* `image` is any `t:Vix.Vips.Image.t/0`
* `platform` is the name of a known social
media platform. See `Image.Social.known_platforms/0`.
* `options` is a keyword list of options.
### Options
* `:type` is the image type within the social
platform for which the image should be resized. See
`Image.Social.image_usages/1`
* All other options are passed to `Image.thumbnail!/3`.
## Returns
* `resized_image` or
* Raises an exception.
"""
@spec resize!(Vimage.t(), platform(), Keyword.t()) ::
Vimage.t() | no_return
def resize!(%Vimage{} = image, platform, options \\ []) do
case resize(image, platform, options) do
{:ok, image} -> image
{:error, reason} -> raise Image.Error, reason
end
end
# ---- Helpers -----
defp get_image_size(platform, :default = type, orientation) do
default = Map.get(platform, type, {nil, type})
get_image_size(platform, default, orientation)
end
defp get_image_size(_platform, {nil, usage}, _orientation) do
{:error, unknown_usage_error(usage)}
end
defp get_image_size(platform, usage, orientation) when is_map_key(platform, usage) do
platform
|> Map.get(usage)
|> resolve_orientation(orientation)
end
defp get_image_size(_platform, usage, _orientation) do
{:error, unknown_usage_error(usage)}
end
defp resolve_orientation(size, _orientation) when is_binary(size) do
{:ok, size}
end
defp resolve_orientation(sizes, orientation) when is_map_key(sizes, orientation) do
{:ok, Map.fetch!(sizes, orientation)}
end
defp resolve_orientation(_sizes, orientation) do
{:error, unknown_orientation_error(orientation)}
end
defp unknown_platform_error(platform) do
"Unknown social platform #{inspect(platform)}"
end
defp unknown_usage_error(:default) do
"No :default usage is configured"
end
defp unknown_usage_error(usage) do
"Unknown image usage #{inspect(usage)}"
end
defp unknown_orientation_error(orientation) do
"Unknown orientation #{inspect(orientation)}"
end
end