[Tex/LaTex] Can fuzzy borders be added to external pictures programmatically

graphicsshadingtikz-pgftransparency

In my opinion, external pictures are often more pleasing to the eye on a white background when they have fuzzy rather than "sharp" borders. For instance, compare this

enter image description here

to that

enter image description here

Adding fuzzy borders is straighforward in image-editing tools such as GIMP (Filters >> Decor >> Fuzzy borders…).

However, using 3rd-party tools for adding fuzzy borders on an image to be included in a .tex file lacks maintainability. What if I suddenly decide that the fuzzy borders I added to my original image are too narrow or too wide, or that I no longer want fuzzy borders? I would have to modify all my image files in a 3rd-party image-editing tool…

Is there a way to add fuzzy borders to external pictures programmatically in LaTeX, without modifying the original image file? That would be nice! You could then tweak some fuzzy-border settings globally, decide whether or not you want fuzzy borders at all, etc. directly inside (rather than outside) your input file.

My attempt: I didn't find any way of using \shade with different opacity for top color and bottom color. Therefore, I thought I could draw concentric rectangular paths of decreasing dimensions and decreasing opacity on top of the picture, but that seems like a very wasteful way of doing things.

I'm not asking for a full solution here, merely for pointers.

Can you think of a better approach?

Related questions on TeX.SE include:

but I haven't been able to adapt the solutions proposed there.

Best Answer

You could use TikZ together with an auxiliary pdf file that contains a square that is filled with a white with full opacity to white with no opacity shade. Then overlay this auxiliary pdf on all four sides, stretching them to the correct size. (UPDATE: See update below for how to do this without the external pdf file).

Using an external pdf file

The code below produces the following output:

enter image description here

\documentclass{article}

\usepackage{tikz}
\usepackage{graphicx}

\begin{document}

\makeatletter

\newlength\pic@height
\newlength\pic@width
\newlength\shade@width
\shade@width=32pt

\begin{tikzpicture}

\node (pic) { \includegraphics{pic.jpg} };

\pgf@process{\pgfpointdiff{\pgfpointanchor{pic}{south east}}{\pgfpointanchor{pic}{north west}}}

\setlength\pic@height{\pgf@y}
\setlength\pic@width{\pgf@x}

\node at (pic.east) [anchor=west, xshift=1pt, rotate=180] 
    { \includegraphics[width=\shade@width, height=\pic@height]{border.pdf}};
\node at (pic.west) [anchor=west, xshift=-1pt] 
    { \includegraphics[width=\shade@width, height=\pic@height]{border.pdf} };
\node at (pic.north) [anchor=west, xshift=-.5\pic@width, yshift=1pt, rotate=-90] 
    { \includegraphics[width=\shade@width,height=\pic@width]{border.pdf} };
\node at (pic.south) [anchor=west, xshift=.5\pic@width, yshift=-1pt, rotate=90] 
    { \includegraphics[width=\shade@width,height=\pic@width]{border.pdf} };

\end{tikzpicture}

\end{document}

This code requires an auxiliary file border.pdf as I described above. To make it look more fuzzy you would have to play with this pdf file.

For better results, it may be nicer to have a separate auxiliary pdf for the corners, although I'm not sure if that will be rendered properly on every pdf viewer, because you would have to place the auxiliary pdfs right next to each other.

Using pgf shading (does NOT work in XeLaTeX! Shading is not supported by XeLaTeX.)

The drawback of the above approach is that you need an external pdf file. On top of that, the corners of the output above look a bit strange if you look closely. This latter has to do with the "derivative" of the opacity parameter in the horizontal direction in my auxiliary pdf file (it is piecewise linear: constant at 1, linearly decrease, constant at 0). Perhaps it is better to let the opacity be a smooth function of the x coordinate (for example the logistic function exp(x)/(1+exp(x))).

The following package smoothpic.sty uses pgf shading, which uses no external pdf file and has smoother shading.

\NeedsTeXFormat{LaTeX2e}[1995/12/01]
\ProvidesPackage{smoothpic}[2013/05/05 Smoothpic]

% This package defines the \smoothpic command which is a wrapper around the 
% \includegraphics command. Any optional arguments given to \smoothpic are 
% passed to \includegraphics.
% Example: \smoothpic[width=5cm]{image.jpg}
%
% To change the width of the shade, change the value of \smoothpicshadewidth.
% Example: \smoothpicshadewidth=32pt
%

\RequirePackage{tikz}
\RequirePackage{graphicx}
\usetikzlibrary{calc}

% ---- Desired width of the shade
\newlength\smoothpicshadewidth
\smoothpicshadewidth=24pt

\newlength\smoothpic@height%
\newlength\smoothpic@width%

% ---- Define horizontal shading, with opacity according 
%      to a logistic function
\pgfdeclarehorizontalshading{smoothpichshading}{100bp}
  { color(0pt)=(transparent!0);
    color(25bp)=(transparent!0);
    color(28bp)=(transparent!1);
    color(31bp)=(transparent!2);
    color(34bp)=(transparent!3);
    color(38bp)=(transparent!6);
    color(41bp)=(transparent!11);
    color(44bp)=(transparent!20);
    color(47bp)=(transparent!33);
    color(50bp)=(transparent!50);
    color(53bp)=(transparent!67);
    color(56bp)=(transparent!80);
    color(59bp)=(transparent!89);
    color(63bp)=(transparent!94);
    color(66bp)=(transparent!97);
    color(69bp)=(transparent!98);
    color(72bp)=(transparent!99);
    color(75bp)=(transparent!100);
    color(100bp)=(transparent!100) }
\pgfdeclarefading{smoothpichfading}{\pgfuseshading{smoothpichshading}}

% ---- Define vertical shading, with opacity according 
%      to a logistic function
\pgfdeclareverticalshading{smoothpicvshading}{100bp}
  { color(0pt)=(transparent!0);
    color(25bp)=(transparent!0);
    color(28bp)=(transparent!1);
    color(31bp)=(transparent!2);
    color(34bp)=(transparent!3);
    color(38bp)=(transparent!6);
    color(41bp)=(transparent!11);
    color(44bp)=(transparent!20);
    color(47bp)=(transparent!33);
    color(50bp)=(transparent!50);
    color(53bp)=(transparent!67);
    color(56bp)=(transparent!80);
    color(59bp)=(transparent!89);
    color(63bp)=(transparent!94);
    color(66bp)=(transparent!97);
    color(69bp)=(transparent!98);
    color(72bp)=(transparent!99);
    color(75bp)=(transparent!100);
    color(100bp)=(transparent!100) }
\pgfdeclarefading{smoothpicvfading}{\pgfuseshading{smoothpicvshading}}

% ---- Define the \smoothpic command
\newcommand\smoothpic[2][]{%
    \bgroup%
    \begin{tikzpicture}
        % --- add node containing the image  
        \node (smoothpic) [inner sep=0.5pt] { \includegraphics[#1]{#2} };

        % --- determine height and width of the image
        \pgf@process{\pgfpointdiff{\pgfpointanchor{smoothpic}{south east}}{\pgfpointanchor{smoothpic}{north west}}}
        \setlength\smoothpic@height{\pgf@y}
        \setlength\smoothpic@width{\pgf@x}

        % --- draw left overlay
        \pgfpathrectangle{\pgfpointanchor{smoothpic}{south west}}{\pgfpoint{\smoothpicshadewidth}{\smoothpic@height}}
        \pgfsetfadingforcurrentpath{smoothpichfading}{} 
        \fill [white] (smoothpic.north west) rectangle ($(smoothpic.south west)+(\smoothpicshadewidth,0)$);

        % --- draw right overlay
        \pgfpathrectangle{\pgfpointanchor{smoothpic}{south east}}{\pgfpoint{-\smoothpicshadewidth}{\smoothpic@height}}
        \pgfsetfadingforcurrentpath{smoothpichfading}{\pgftransformrotate{180}}
        \fill [white] (smoothpic.north east) rectangle ($(smoothpic.south east)+(-\smoothpicshadewidth,0)$);

        % --- draw top overlay
        \pgfpathrectangle{\pgfpointanchor{smoothpic}{north east}}{\pgfpoint{\smoothpic@width}{-\smoothpicshadewidth}}
        \pgfsetfadingforcurrentpath{smoothpicvfading}{\pgftransformrotate{180}}
        \fill [white] (smoothpic.north west) rectangle ($(smoothpic.north east)+(0,-\smoothpicshadewidth)$);

        % --- draw bottom overlay
        \pgfpathrectangle{\pgfpointanchor{smoothpic}{south east}}{\pgfpoint{\smoothpic@width}{\smoothpicshadewidth}}
        \pgfsetfadingforcurrentpath{smoothpicvfading}{}
        \fill [white] (smoothpic.south west) rectangle ($(smoothpic.south east)+(0,\smoothpicshadewidth)$);
    \end{tikzpicture}%
    \egroup%
}

This package can be used as follows:

\documentclass{article}
\usepackage{smoothpic}
\begin{document}
\smoothpicshadewidth=24pt
\smoothpic{pic.jpg}
\end{document}

I generated the logistic opacity sequence using the following python code: (M is the number of piecewise linear parts of the opacity function, and C is a steepness parameter) I provide this code only for reference, you don't need it to compile the LaTeX code.

import math
M = 16
C = 11
for i in range(0, M+1):
    x = (float(i) / M - 0.5)
    p = round(50 + 50 * x)
    opacity = round(100* math.exp(C*x) / (1+math.exp(C*x)))
    print "    color(%dbp)=(transparent!%d);" % (p, opacity)

This renders as:

enter image description here

Which looks a bit nicer, and does not use an external pdf.

Related Question