Jeff Mesnil
Weblog · About

How To Shoot a Bottle of Beer — St. Stefanus Blonde

September 5, 2014
Une Petite Mousse

For the past 18 months, I have photographed bottles of beer for my friends of Une Petite Mousse. On their web site, you subscribe to receive every month a box of six bottle of beers. The selection changes every month and contains a mix of well-known beers and others from micro-breweries. It is a great way to discover and taste new beers that you can not find in a store near you.

They need pictures of the bottles for their online catalogue and the paper guide that comes in the boxes.

Making photographs of a bottle of beer is suprisingly challenging. I want to give on overview of the color of the beer inside the bottle (which are often made of a dark glass) while highlighting the label on the front of the bottle. In order to build the catalogue, I want to have an uniform style for all pictures with a pure white background and a square format (to keep the respective size of the bottles).

In this post, I'll explain the process I have used for the latest box (available mid-september). For september 2014, one of the beers is the St. Stefanus Blonde, brewed in Belgium.

St. Stefanus Blonde
St. Stefanus Blonde © St Stefanus

The picture above is from their web site (I really like how the strips of white emphasize the roundness of the small bottle).


To photograph this bottle, I used the following material:

  • Fuji X-E2 camera
  • Fuji 56mm ƒ/1.2 lens (which is my lens with the longest focal length)
  • 2 Flash Speedlights (Yongnuo YN560-III and Nikon SB-700) in manual mode. I plugged the Nikon SB-700 on a Yongnuo RF-603/N3 to trigger it remotely
  • a Yongnuo YN560-TX plugged on the camera hotshoe to control remotely the speedlights
  • my trusty Giotto's Vitruvian tripod to stabilize the camera
  • many white foam boards (for the background and below the bottle)
  • many black foam boards (used as gobos to block the light)

Before explaining my process, I heartily recommend to read Light Science and Magic: An Introduction to Photographic Lighting. This book is an invaluable help to understand how light plays with shapes and textures.


The first step is to place the bottle and the various white and black foam boards that will give the bottle a distinctive shape. In order to highlight the color of the beer inside the bottle, I will light white foam boards in the background. The light will bounce on the boards and go through the bottle before reaching the camera. The background will be overexposed to be pure white.

I also want to have well-defined edges on the bottle. I place two black foam boards on each side of the white background. These black boards will appear on the edges of the bottle and increase the contrast and sharpness of its shape. Without these black boards, the light coming from the white background would splill on the edges and make them too soft.

I put the bottle on a white foam board (with a plexiglass to have a small reflection at the bottom). Finally, I put other black foam boards on each side of the bottle and on top of it to prevent any leaking light to reach it.

I then place the camera at the right distance of the bottle to have it fill the image height. The image will be cropped to square in post-processing. I activate the X-E2 guides on its screen to have a good overview of the final size.

Setup © Jeff Mesnil

Once the camera is correctly placed, I fine tune the placement of the black boards on each side of the white background. They must be placed so that the edges of the bottle are dark but not too close so that they fill the inside of the bottle.

Placement of the black foam boards
Placement of the black foam boards © Jeff Mesnil

I put the bottle on top of a small box to be able to place the flashes below it (so that they do not appear on the photos) and angle them appropriately to light the scene.

I want to control the lighting and block any ambient light (the garage where I took the photos has lights coming from windows that can not be easily blocked). I configure my camera to 1/180 (the highest sync speed for the X-E2) and ISO 400. The lens is stopped down to ƒ/8 to have enough depth of field.

With these settings, the image is so dark that I know I will not be bothered by any ambient light (the photo is cropped but no other postprocessing has been applied).

Ambient Light Only
Ambient Light Only © Jeff Mesnil

Next step is to place the two flashes and provide the lighting.

Back (Main) Light

The main light is provided by the flash behind the bottle. It is directed towards the white background and will light the bottle after bouncing on the background. I use the Yongnuo YN560-III zoomed at 24 mm with a diffuser to spread the light on the white background.

When I take the picture, I can see that the beer inside the bottle is well lit and the background is overexposed to pure white. Thanks to the black foam boards, the edges of the bottle are dark and sharp enough (the photo below is cropped but no other postprocessing has been applied).

Back (Main) Light
Back (Main) Light © Jeff Mesnil

Front (Fill) Light

The liquid inside the bottle is lit but there is no light reaching the label on the front of the bottle.

I then to use the second flash (Nikon SB-700) to light only the label. The placement of the flash is tricky because it can easily create highlights on the glass if it is not properly directed. I place it below the bottle with an angle of 45° or so. It is fully zoomed at 135 mm to focus its light on the label. With this angle and zoom, the flash will lit the paper label but most of the light reaching glass will not be captured by the camera.

The photo below shows the bottle lit by the front flash only (the photo below is cropped but no other postprocessing has been applied).

Font (Fill) Light
Font (Fill) Light © Jeff Mesnil

We can see there are two highlights on the bottom of the bottle and near its neck. They are small enough that I will be able to remove it in post processing.

Final Picture

Once the two speedlights are setup, I tweak their power to have a correct ratio between the back and front lights. At then, I use the following ratio:

  • Back (main) light 1/16 +0.3EV
  • Front (fill) light 1/64 -0.3EV

When I take the picture with both the back and front lights, I capture the final (cropped but unprocessed) picture below.

Back and Front Lights
Back and Front Lights © Jeff Mesnil

Most of the work is now done and I just need a few touches of post-processing to finalize the image.


I use Lightroom for the image post-processing.

Once the image is imported, I correct the lens distorsions and crop the image to a square format.

I slightly modify its global setting in the Basic module:

  • I fix its white balance (using a photo of the scene with a grey card)
  • I punch its Tone
    • +0.75 Exposure
    • +41 Contrast
    • +26 White
    • -63 Black
  • I increase its Presence
    • +16 Clarity
    • +26 Vibrance
    • -4 Saturation
  • I also increase the sharpness in the Details module (+52 Sharpening).

The numbers are meaningless. They are different for each bottle, depending mainly on the color and texture of the liquid and glass. The goal with any of these settings is to have a pure white background and a bottle that is well exposed and contrasted.

I use the Spot Removal tool to remove the highlights caused by the front flash at the bottom of the bottle and near its neck.

Last step is to use a few Adjustment Brushes to finalize the images:

  • darken the bottle reflection on the plexiglass
  • lighten the bottom corners of the label (that's a mistake I did not catch when capturing the image)
  • sharpen and increase contrast of the label (to have the text standout)

I finally export it in JPEG and hand it over to Barbara, the graphist designer of Une Petite Mousse, that uses it for the paper guide and the web site.

St. Stefanus Blonde
St. Stefanus Blonde © Jeff Mesnil

Since March 2013, I have photographed more than one hundred different beers1 and each one is a different challenge. The shape of the bottle changes, the color of the liquid and the glass change too. Some have dark labels, others have golden labels. Some do not have labels at all and the text is put directly on the glass.

I am always tweaking and refining the process to capture all these subtle differences and I am sure that I will continue to do so for as long as I take pictures and enjoy it.

Santé! 🍻

  1. and I have tasted all of them! :) 

Macarons au Chocolat

April 21, 2014

Macarons au Chocolat
Macarons au Chocolat © Jeff Mesnil

I am experimenting how to light a dark background and had no other subjects on hands than the macarons we just cooked :)


March 12, 2014

Architecture © Jeff Mesnil

Les Trois Pucelles

February 24, 2014

Les 3 Pucelles, the Three Maiden, are three rocks high above Grenoble in the Vercors. It was an important location used by the Resistance during the 2nd World War.

Les 3 Pucelles
Les 3 Pucelles © Jeff Mesnil

This 90-metre springboard was used for the ski jumping competion during the 1968 Winter Olympic Games at Grenoble. It has been closed since the 80s and can no longer be used.

Tremplin des 3 Pucelles
 Tremplin des 3 Pucelles © Jeff Mesnil

La Fourchette d'André Kertész

January 27, 2014

Yesterday, I was reading a book about André Kertész, one of my favourite photographers. I tried to reproduce one of his most famous photographs, La Fourchette, that he made in 1928.

La Fourchette
La Fourchette d'André Kertész © Jeff Mesnil

The shape of my fork and plates are different from the original ones but the lighting is pretty similar. I used a single strobe to get a unidirectional hard light and tried several positions to get the shadows of the plate, the fork and the teeth close to the original:

La Fourchette
La Fourchette, 1928 © André Kertész

The printing I have is more subtle and nuanced that this picture from Wikimedia that has too much contrast.

2013 in Photos

January 2, 2014

2013 has ended and it's the opportunity to look back at all the photos I made during the year.

They are mostly landscapes from our trips (Tuscany, Brittany, Barcelona), food and a plethora of portraits of Marion :)

I expect more of the same in 2014 and continue to improve my craft and skills.

Rainy Sunday Cookies

September 29, 2013

There is nothing better for a rainy sunday than cooking some cookies with my girlfriend and make pictures of them to play with my flash and reflectors.

Rainy Sunday Cookies
Rainy Sunday Cookies © Jeff Mesnil

Let's eat them now!

stomp.js for node.js apps

September 25, 2013

stomp.js is a simple JavaScript library to send and receive STOMP messages from a Web browser using Web Sockets.

Today, I have released its version 2.3.0 that adds support for node.js. This makes it possible to send and receive STOMP message from any node.js app by connecting to a STOMP broker on its TCP port (usually 61613) or on its Web Socket.

I have registered a npm package stompjs that can be installed by typing:

npm install stompjs

and in the code, requiring the module:

var Stomp = require('stompjs');

To connect to a STOMP broker over a TCP socket, use the Stomp.overTCP(host, port) method:

var client = Stomp.overTCP('localhost', 61613);

To connect to a STOMP broker over a Web Socket, use instead the Stomp.overWS(url) method:

var client = Stomp.overWS('ws://localhost:61614/stomp');

Apart from this initialization, the STOMP API remains the same whether it is running in a Web browser or in node.js application.

A simple node.js app that sends and receives a STOMP message can be coded in a few lines:

var Stomp = require('stompjs');

// Use raw TCP sockets
var client = Stomp.overTCP('localhost', 61613);
// uncomment to print out the STOMP frames
// client.debug = console.log;

client.connect('user', 'password', function(frame) {
  console.log('connected to Stomp');

  client.subscribe('/queue/myqueue', function(message) {
    console.log("received message " + message.body);

    // once we get a message, the client disconnects
  console.log ('sending a message');
  client.send('/queue/myqueue', {}, 'Hello, node.js!');

In this example, the client connect to the STOMP broker on its TCP socket by calling Stomp.overTCP(host, port):

var client = Stomp.overTCP('localhost', 61613);

To connect on its Web Socket, you only need to change the creation of the client by calling instead Stomp.overWS(url):

var client = Stomp.overWS('ws://localhost:61614');

This means that if your code uses stomp.js, you can run the same code in the Web browser or in node.js That may prove handy for testing...

Why another STOMP client for node.js when there are already a dozen?

I believe the code of stomp.js is already the best of them.

It is distributed by the major STOMP brokers (ActiveMQ, Apollo, HornetQ, and RabbitMQ), widely used, thoroughly tested and documented.

The STOMP protocol implementation is the same whether the client is running in a Web browser or in node.js. The only differences are the timers and the socket implementations (native Web Socket for Web browser, net.Socket for node.js). The socket implementation can still be customized and many users run it over SockJS

There are likely some corner cases to iron out but the main features (including heart-beating) should work as expected.

Note that the node.js support is done outside the stomp.js file. If you only need to use STOMP from the Web browser, this changes nothing: you only need that file (or its minified version).


Writing a Book for O'Reilly about Mobile & Web Messaging

September 9, 2013

The title says it all: I've agreed with O'Reilly Media to write a book about Mobile and Web Messaging.

Almost all my career has been spent developing messaging platforms or clients using messaging. The last few years, I have focused on messaging for Mobile and Web platforms.

  • I added STOMP support to HornetQ to be able to send and receive messages from iOS and Android apps.
  • I wrote stomp.js to send and receive messages from HTML5 Web Browsers1. This small library is now used by the main Open Source messaging brokers (ActiveMQ, Apollo, RabbitMQ in addition to HornetQ).

This book is the result of all this work and will help mobile and Web developers leverage messaging protocols in their applications.

I plan to introduce messaging protocols and write about STOMP (and most likely MQTT too) in details. The book will come with examples for mobile platforms and Web browsers.

I have setup a web site at to promote the book and will tweet about it at @mobilewebmsg.

The target release for the book is June 2014.

For a long time, I wanted to write a technical book and it is a chance to do it about an interesting subject and be published by the best editor for programming books. O'Reilly agreed to publish the book under an Open Source license and the source and examples will be hosted on GitHub (when I have some material to show).

This opportunity is only possible because my employer, Red Hat, allows me to spend some of my work time on this book. Red Hat is an awesome company to work for and we are hiring!

I am incredibly excited about this book and look forward to sharing some sample chapters. I just need to start writing them now! :)

  1. That's why I just released a new version of stomp.js. I plan to write a chapter about it and found some shortcomings that I wanted to fix. 

stomp.js 2.1.0 is Released

September 3, 2013

I have released a new version of Stomp over WebSockets.

During my summer holidays, I have simplified its API to make it more object-oriented instead of a simple translation of the protocol frames. The new API is backwards compatible except for the susbcription change.

The documentation has been updated to reflect the new API.

I also created a 2.1.0 release on GitHub to provide a well-known location to download stomp.js (and its minified version).


The connect() method accepts different number of arguments to provide a simple API to use in most cases:

client.connect(login, passcode, connectCallback);
client.connect(login, passcode, connectCallback, errorCallback);
client.connect(login, passcode, connectCallback, errorCallback, host);

where login, passcode are strings and connectCallback and errorCallback are functions (some brokers also require to pass a host String).

The connect() method also accepts two other variants if you need to pass additional headers:

client.connect(headers, connectCallback);
client.connect(headers, connectCallback, errorCallback);

where header is a map and connectCallback and errorCallback are functions.

Please note that if you use these forms, you must add the login, passcode (and eventually host) headers yourself:

var headers = {
  login: 'mylogin',
  passcode: 'mypasscode',
  // additional header
  'client-id': 'my-client-id'
client.connect(headers, connectCallback);


The message objects passed to the subscribe callback now have ack() and nack() methods to directly acknowledge (or not) the message.

var sub = client.subscribe("/queue/test",
  function(message) {
    // do something with the message
    // and acknowledge it
  {ack: 'client'}


Instead of returning an id from client.subscribe(), subscribe() returns an object with an id property corresponding to the subscription ID and an unsubscribe() method.

var subscription = client.subscribe(...);


The begin() method returns a JavaScript object with commit() and abort() methods to complete the transaction.

var tx = client.begin();
message.ack({ transaction:, receipt: 'my-receipt' });
tx.commit(); // or tx.abort();

A transaction ID is automatically generated when calling 'begin() and is available in the returned object's id property.

Miscellaneous changes and fixes

  • default onreceive method

    When a subscription is automatically created on the broker-side, the received messages are discarded by the Stomp.client that find no matching callback for the subscription headers. To workaround that, the client can set a default onreceive callback that will be called if no matching subscription is found.

    This is required to support RabbitMQ temporary queue destinations.

  • By default, debug messages are now written in window.console.

  • STOMP can now be used from a WebWorker (an example shows how to use it).

  • STOMP frame fragmentation

    If the STOMP frames are big to send on a single WebSocket frame, some web server may have trouble process them. It is now possible to split a STOMP frame on multiple Web Socket frames by configuring the client.maxWebSocketFrameSize (in bytes). If the STOMP frame is bigger than this size, it will be send over multiple Web Socket frames (default is 16KiB).

  • use the 1st value for repeated headers

    Stomp.js was keeping the last value of repeated headers. This has been fixed according to the specification to take the 1st value of repeated headers.

  • fix generation of timestamp on IE8

Sunset At Saint-Malo

August 8, 2013

Sunset At Saint-Malo
 Sunset At Saint-Malo (France) © Jeff Mesnil

Velours de Mangue

August 5, 2013

We bought a siphon for cooking some time ago and are having fun with it to prepare desserts and starters (we made a mousse de foie gras that was awesome). Yesterday, we prepared a velours de mangue (velvet of mango).

Velours de Mangue
Velours de Mangue © Jeff Mesnil

JMS 2.0: Shared Subscription

June 27, 2013

JMS 2.0 has been released a few weeks ago. We are working hard to support it in HornetQ and WildFly and I plan to write a few articles about it.

The main features are described on Oracle Web site in What's New in JMS 2.0, Part One: Ease of Use and Part Two: New Messaging Features.

One of the most important new features is Shared Subscription.

Let's assume you are using a topic to send messages. There are several components subscribed to this topic that are processing the messages in different fashion (for example to process jobs A and B).

When a message M is sent to the topic, all consumers subscribed to the topic receive it:

                                   M   +--------------+
                                  ---> | consumer CA1 |  < a consumer for the job A >
    +----------+  M   +-------+ /      +--------------+
    | producer | ---> | topic |   
    +----------+      +-------+ \  M   +--------------+
                                  ---> | consumer CB1 | < a consumer for the job B >

The consumer CB1 is subscribed to the topic. Every time, it receives a message, it processes the job B.

Unfortunately, this job is time-consuming and we would like to scale it out by having several consumers for the job B running on different machines.

We can not have several consumers on the topic for this job, otherwise they would all receive copies of the same message and process it several times. What we need is a set of consumers that receives a single message from the topic amongst them.

If the component is running inside a Java EE application server, you can use a Message-Drive Bean for that. If the component is a Java SE application, you are out of luck.

Before JMS 2.0, each messaging brokers has different ways to provide this feature.

With HornetQ, the workaround is to use a divert:

Diverts allow you to transparently divert messages routed to one address to some other address, without making any changes to any client application logic.

We use a divert to route the messages sent to the topic to another queue (let's called it Queue_B since it's for the processing job B). Everytime a message is sent to the topic, a copy is also routed to the queue. Our divert is non-exclusive since we want it to still be received by the topic consumers.

We can then create as many consumers we want for Queue_B. Since this destination is a queue, only one of them will receive a message that was initially sent to the topic:

                                   M   +--------------+
                                  ---> | consumer CA1 |
    +----------+  M   +-------+ /      +--------------+
    | producer | ---> | topic |  
    +----------+      +-------+  
                          \                        +--------------+
                           \ M                ---> | consumer CB1 |
                     divert \               /      +--------------+
                             \ +---------+ /       +--------------+  
                               | Queue_B | -- ---> | consumer CB2 |
                               +---------+ \       +--------------+
                                            \  M   +--------------+
                                              ---> | consumer CB3 |

Among CB1, CB2 and CB3, only CB3 received the message sent to the topic.

There are still many shortcomings of using diverts (or similar features) to share consumers of a topic.

  • for each set of shared consumers, a system administrator must create and administrate on the server one more divert and one more queue.
  • the shared consumers are no longer consuming from the original topic but from a specific queue. That makes the messaging topology more complex to setup and configure in the client applications.
  • the management of the messaging system becomes also more complex: there is no way to know how many subscribers are "really" listening to the topic, this information is lost when messages are routed by the divert.

Enter JMS 2.0 and its "shared subscription".

A non-durable shared subscription is used by a client which needs to be able to share the work of receiving messages from a topic subscription amongst multiple consumers. A non-durable shared subscription may therefore have more than one consumer. Each message from the subscription will be delivered to only one of the consumers on that subscription.

The basic idea behing sharing a subscription is to have a set of consumers (identified by a shared subscription name) receiving exclusively a message from a topic.

That solves all the issues we originally had in a standard fashion. We can now create many shared consumers with the shared subscription name << sub B >> to have them process messages for the job B and scale out nicely:

                                            M         +--------------+
                                    ----------------> | consumer CA1 |
                                  /                   +--------------+
                                 /                    +--------------+
    +----------+  M   +-------+ /                ---> | consumer CB1 |
    | producer | ---> | topic |                /      +--------------+
    +----------+      +-------+ \             /   M   +--------------+
                                  << sub B >> -- ---> | consumer CB2 |
                                              \       +--------------+
                                               \      +--------------+
                                                 ---> | consumer CB3 |

Among the shared consumers CB1, CB2 and CB3, only CB2 received the message sent to the topic.

  • all our consumers are directly consuming from the topic
  • there are no additional resources to configure or administrate on the server

The JMS 2.0 API adds new methods to create shared consumer:

    JMSContext.createSharedConsumer(Topic topic, String sharedSubscriptionName)
    JMSContext.createSharedConsumer(Topic topic, String sharedSubscriptionName, String messageSelector)

It is also possible to create shared durable consumers:

    JMSContext.createSharedDurableConsumer(Topic topic, String name)
    JMSContext.createSharedDurableConsumer(Topic topic, String name, String messageSelector)

Sharability and durability of subscriptions are two orthogonal notions and all the combination are possible:

  • a unshared non-durable subscription can only have one consumer that will not receive messages sent while its consumer was offline
  • a unshared durable subscription can only have one consumer that will receive the messages sent while its consumer was offline
  • a shared non-durable subscription can have many consumers that will not receive messages sent while all consumers were offline.
  • a shared durable subscription can have many consumers that will not receive the messages sent while all consumers were offline.

JMS 2.0 is an incremental update to JMS that should simplify any Java application using messages. Shared subscription is one of this interesting new features.

⇨ Tim Duncan — The Big Fundamental

June 3, 2013

Insightful portrait of Tim Duncan on the cusp of his 5th NBA finals (he won his first fours):

Sometimes people will mention the swimming when trying to pinpoint the origin of Duncan’s remarkable discipline and relentless consistency. Duncan swam until he was 14 and seemed on a path toward the Olympics like his sister, Tricia. That was when Hurricane Hugo roared through the Island and wrecked the swimming pool. More, that was the year his mother, Ione, died of cancer. She had been his greatest fan, the loudest cheers at the pool, the voice inside his head. He would talk about the little nursery rhyme that she would repeat to him over and over and over …

Good, better, best
Never let it rest
Until your good is better
And your better is your best

You can’t help but think that if you cut Tim Duncan open, those words would be etched on his heart.

There has never been a basket-ball player so consistent for the whole span of his career (stats per 36 minutes):

  • 1st year - 21 year old, 19.4 points, 11 rebounds, 2.5 assists, 2.4 blocks, 57.7 true shooting %
  • 7th year - 27 year old, 21.9 points, 12.2 rebounds, 3.0 assists, 2.6 blocks, 53.4 true shooting %
  • 17th year - 36 year old, 21.3 points, 11.9 rebounds, 3.2 assists, 3.2, blocks, 55.4 true shooting %

I am rooting for the spurs for these finals! #GoSpursGo

(via kottke)

Uriage Cabriolet Classic

May 27, 2013

This sunday was held the 2013 edition of Uriage Cabriolet Classic, a large gathering of classic (and more recent) cars.

I am not a connaisseur of cars but I can recognize a beautiful one when I see it. I really enjoy the attention to details of these cars and the love and care of their owners (the engines were all shining!).

Leather strap for car hood
Leather strap for car hood © Jeff Mesnil

Star on car hood
Star on car hood © Jeff Mesnil

Car Engine
Car Engine © Jeff Mesnil

This gathering is an opportunity to appreciate all the shapes and colours of cars. Even the most modest cars look fantastic with out of the ordinary colours.

Citro&euml;n 2 CV
Citroën 2 CV © Jeff Mesnil

Citro&euml;n 2 CV
Citroën 2 CV © Jeff Mesnil

We were lucky having a beautiful weather for the gathering and enjoyed the familial atmosphere.

Blue child &amp; car
Blue child & car © Jeff Mesnil

Tuscany Countryside

May 13, 2013

Tuscany Countryside
 Tuscany Countryside © Jeff Mesnil

My girlfriend and I spent one week in Tuscany. We loved every moment of it.

⇨ David duChemin Reviews the Fuji XE-1

May 13, 2013

Spoiler: he loves the camera and, unsurprisingly, makes beautiful pictures with it.

I bought his book, The Print and the Process, this week-end. It is a beautifully crafted book composed of 4 sections, Venic, Iceland, Kenya, and Antarctica providing a variety of scenes (it's mostly landscapes and portraits though, it's a David duChemin's book after all :) Each section is composed of a set of 20-30 images without any text that helps appreciate the flow of pictures without any interruption. At the end of each section, there is a text for each images where the author describes what his intent or feeling was when he took the picture.

The book is bound along the short edge in landscape mode. This design makes the beautiful landscape images really stand out.

Beautiful book, gorgeous photos and instructive reading. Heartily recommended.