Automated vCard writer (with photo)

Since I posted this yesterday (in the wrong thread naturally…), I’ve been obsessing a little over trying to cram as much data and as best a photo as possible on a limited size NTAG. Classic hacker pursuit: there isn’t nearly enough enough space on an NFC tag for a decent vCard photo, so I just had to do it :slight_smile:

I’ve been trying different JPEG encoders, different mugshot sizes, contrasts etc… I played the what-if game manually for a while - that is, change something, regenerate the vCard by hand, program it onto the tag by hand, try it on the cellphone, failure, rinse, repeat.

That got old pretty quickly, so I wrote a script to do everything in one go, test the size of the final NDEF and write it onto a tag. It makes trying to exploit as much of the tag’s memory space as possible quite easy and fast. So I figured I’d share it, if anybody’s interested.

The script is Linux-only. Sorry, if you have another OS, I can’t help you. But if you do run Linux, read on.

Before you use it, you need a few things installed:

  • Imagemagick: it should come installed by default in most Linux distros. If not, you can do something like apt-get install imagemagick as root (for Debian-based distros - your mileage may vary)

  • Guetzli: this is an advanced JPEG encoder released by Google in 2017 that really cuts down on size and preserve the quality a lot better than libjpeg. It’s not as good as JPEG2000, but JPEG2000 files aren’t supported natively in Android, so it wasn’t an option anyway. Guetzli really is quite good, and by far the best JPEG encoder I’ve found.

    There is a pre-built Guetzli package, but unfortunately, by default Guetzli refuses to lower the JPEG quality enough and tells you to get the source and hack it :frowning: So what you need to do is this (as an ordinary user):

    • Get the code from Github: git clone https://github.com/google/guetzli.git
    • Edit guetzli/guetzli/processor.cc, look for a line that says if (params.butteraugli_target > 2.0f) { and replace it with if (0) {
    • Compile it normally with make. No need to install it, the script below assumes it’s in the current directory
  • ndeflib: it’s a Python module. Install it by doing pip install ndeflib and pip3 install ndeflib as root (you need both the Python2 and Python3 versions).

  • ndeftool: another Python utility. Install it by doing pip3 install ndeftool as root.

  • nfcpy: do pip3 install nfcpy as root.

  • tagtool.py: normally it’s part of nfcpy, but it’s not packaged in the pip distribution for some reason. You need to get it from the Github repo. Simply do git clone https://github.com/nfcpy/nfcpy.git in the same directory you pulled Guetzli in as a normal user.

Finally, to create the vCard, encode it into an NDEF record and write it to a tag, copy / paste the following script in the directory where Guetzli and nfcpy reside:

#!/bin/bash

### vCard information
FIRSTNAME=Joe
LASTNAME=Blow
TELEPHONE="(311) 555-1212"
EMAIL=joeblow@aol.com
PHOTO=./photo.jpg
NOTE=

### Final size of the photo on the tag
TAG_PHOTO_SIZE=50x50

### Quality of the compressed photo on the tag
JPEG_QUALITY=72

### Path to the various utilities we need
GUETZLI=./guetzli/bin/Release/guetzli
NDEFTOOL=ndeftool
TAGTOOL="python3 nfcpy/examples/tagtool.py"
 


# Resize / compress the original photo
echo "Resizing the photo"
convert $PHOTO -resize $TAG_PHOTO_SIZE ./photo_resized.png

echo "Compressing the photo"
$GUETZLI --quality $JPEG_QUALITY ./photo_resized.png ./photo_compressed.jpg

# Create the .vcf file
echo "Creating the vCard file"

echo "BEGIN:VCARD" > vcard.vcf
echo "VERSION:2.1" >> vcard.vcf
echo "N:$LASTNAME;$FIRSTNAME" >> vcard.vcf
echo "TEL:$TELEPHONE" >> vcard.vcf
echo "EMAIL:$EMAIL" >> vcard.vcf

B64ENCPHOTO=$(base64 -w0 ./photo_compressed.jpg)
B64ENCPHOTO="PHOTO;ENCODING=BASE64;TYPE=JPEG:$B64ENCPHOTO"
echo $B64ENCPHOTO | head -c 73 >> vcard.vcf
for L in $(echo $B64ENCPHOTO | tail -c +74 | sed -e 's/.\{72\}/&\n/g'); do
  echo -en "\n $L" >> vcard.vcf
done
echo >> vcard.vcf

if [ "$NOTE" ];then
  echo "NOTE:$NOTE" >> vcard.vcf
fi

echo "END:VCARD" >> vcard.vcf

# Dump the size of the .vcf file, for information
VCARDSIZE=$(wc -c < vcard.vcf)
echo "vCard size: $VCARDSIZE bytes"

# Create the NDEF record
echo "Encapsulating the vCard into an NDEF record"
$NDEFTOOL tn text/vcard pl "$(cat vcard.vcf)" > vcard.ndef

# Dump the size of the NDEF file, for information
NDEFSIZE=$(wc -c < vcard.ndef)
echo "NDEF size: $NDEFSIZE bytes"

# Read the tag
echo "Reading the tag"
CARD_INFO=$($TAGTOOL)

# Make sure the tag is writable
if [ ! "$(echo $CARD_INFO | grep -i 'NDEF Capabilities.*writeable *= *yes')" ]; then
  echo "Tag is not writeable!"
  exit -1
fi

# Make sure there's enough room on the tag for the NDEF record
TCAPA=$(echo $CARD_INFO | sed -r 's/^.*NDEF Capabilities.*capacity *= *([0-9]+) byte.*$/\1/')
if [ $TCAPA -lt $NDEFSIZE ]; then
  echo "NDEF size exceeds the tag's capacity ($TCAPA bytes)!"
  exit -1
fi

# Ask the user if they really want to write the vCard on the tag
read -p "Write NDEF onto the tag [Y/N]? " YN
if [ "$YN" != "y" ] && [ "$YN" != "Y" ]; then
  echo "Aborting"
  exit 0
fi

# Write the NDEF file onto the tag
echo "Writing NDEF onto the tag"
$TAGTOOL load vcard.ndef

echo "All done!"

Name it, say, write_vcard_to_tag.sh, then make it executable by doing chmod +x write_vcard_to_tag.sh.

To use it, supply a photo - preferably with a square aspect ratio - called photo.jpg. Then edit your vCard information at the beginning of the script, and run it. It’ll generate the vCard, then the NDEF record, then try to write it onto the tag.

If the NDEF record ends up being too large, it’ll tell you and stop. If that happens, go back to the script and tweak the size of the photo and the JPEG quality until it fits and you’re satisfied with the resulting image.

The aforementioned utilities use libnfc to write to the tag. I’ve only tried it with an ACR122U, but I suppose it’ll work just as well with other readers supported by libnfc. In any case, if you don’t have that going yet, you’ll have to set it up first.

After playing with it for a while, I managed to come up with a vCard that contains my personal information, a 50x50 photograph in which I’m reasonably recognizable even after blowing it up, and my blood type in the vCard’s note field, with only 2 bytes to spare on an NTAG216 :slight_smile:

That’ll be the vCard I’ll be putting on my doNExT: it’ll serve for snazzy contact exchange of course, but also for first-aid professionals if I’m found unconscious somewhere and they happen to carry a cellphone (and can I convince myself to have an NFC logo tattoo done over the implant… Not too sure about that one yet).

I hope the above will be of interest.

2 Likes

I’m sure it will, Great share,
Thanks very much.

50x50=

or

image

1 Like

Yeah but the quality is a lot more shite than those clean icons. Remember, it’s an overly compressed JPEG: it’s blurry and full of compression artifacts. Not much you can do in 525 bytes. Still, it looks surprisingly good as a small image on a cellphone.

This is the photo I have on my vCard (blurry enough that I don’t mind posting it too much, for a change :slight_smile:):

photo_compressed

Awesome, I was going to ask, but didn’t think you would.

I hope you don’t mind, I blew it up,
and it made you look like a character from Guess Who

image image

1 Like

Looks like I shouldn’t’ve :slight_smile:

Never gets old. :ok_hand:

1 Like

I’ve done some more research and testing, and I found a two ways to increase photo quality even more:

1/ Decrease the image saturation: the end result looks less colorful, but the size of the compressed image decreases significantly

2/ Change file format from JPEG to WEBP. I’ve done tests on a few Android cellphone, and it turns out they all support WEBP natively down to at least Android v5. And boy! is WEBP better than JPEG or what! For the same final size, a WEBP image has a higher resolution, and much fewer compression artifacts than a JPEG.

Here for example, a photo of our Glorious Leader, before (JPEG, 686 x 686, 315 KB) and after (WEBP, 60 x 60, 555 bytes - fits on an NTAG 216):

It looks fantastic on a cellphone. Fucking brilliant image format: it really blows JPEG out of the water.

So, here’s an updated version of my script, that does both JPEG and WEBP - but does WEBP by default. All you gotta do is stick your photo as a photo.jpg file, edit your information, and play around with the compression parameters until the NDEF fits on your tag. In addition to the packages listed in the first post of this thread, you’ll need the webp package (apt-get install webp should do the trick):

#!/bin/bash

### vCard information
FIRSTNAME=Joe
LASTNAME=Blow
TELEPHONE=(311)555-1212
EMAIL=joeblow@aol.com
PHOTO=./photo.jpg
NOTE=

### What type of photo encoding/compression to use (JPEG or WEBP)
PHOTO_ENCODING=WEBP

### Parameters for the final JPEG image to put in the vCard, if we do JPEG
JPEG_SIZE=52x52
JPEG_SATURATION=43
JPEG_QUALITY=73

### Parameters for the final WEBP image to put in the vCard, if we do WEBP
WEBP_SIZE=60x60
WEBP_SATURATION=56
WEBP_QUALITY=69

### Path to the various utilities we need
GUETZLI=./guetzli/bin/Release/guetzli
CWEBP=cwebp
NDEFTOOL=ndeftool
TAGTOOL="python3 nfcpy/examples/tagtool.py"
 


# Resize / desaturate the original photo
echo "Resizing the photo for JPEG"
convert $PHOTO -resize $JPEG_SIZE -modulate 100,$JPEG_SATURATION,100 \
	./photo_resized_for_jpeg.png

echo "Resizing the photo for WEBP"
convert $PHOTO -resize $WEBP_SIZE -modulate 100,$WEBP_SATURATION,100 \
	./photo_resized_for_webp.png

echo "Compressing the photo for JPEG"
$GUETZLI --quality $JPEG_QUALITY ./photo_resized_for_jpeg.png \
	./photo_compressed.jpg

echo "Compressing the photo for WEBP"
$CWEBP -q $WEBP_QUALITY ./photo_resized_for_webp.png -o \
	./photo_compressed.webp

# Create the .vcf file
echo "Creating the vCard file"

echo "BEGIN:VCARD" > vcard.vcf
echo "VERSION:2.1" >> vcard.vcf
echo "N:$LASTNAME;$FIRSTNAME" >> vcard.vcf
echo "TEL:$TELEPHONE" >> vcard.vcf
echo "EMAIL:$EMAIL" >> vcard.vcf

if [ $PHOTO_ENCODING = JPEG ];then
  B64ENCPHOTO=$(base64 -w0 ./photo_compressed.jpg)
else
  B64ENCPHOTO=$(base64 -w0 ./photo_compressed.webp)
fi
B64ENCPHOTO="PHOTO;ENCODING=BASE64;TYPE=$PHOTO_ENCODING:$B64ENCPHOTO"
echo $B64ENCPHOTO | head -c 73 >> vcard.vcf
for L in $(echo $B64ENCPHOTO | tail -c +74 | sed -e 's/.\{72\}/&\n/g'); do
  echo -en "\n $L" >> vcard.vcf
done
echo >> vcard.vcf

if [ "$NOTE" ];then
  echo "NOTE:$NOTE" >> vcard.vcf
fi

echo "END:VCARD" >> vcard.vcf

# Dump the size of the .vcf file, for information
VCARDSIZE=$(wc -c < vcard.vcf)
echo "vCard size: $VCARDSIZE bytes"

# Create the NDEF record
echo "Encapsulating the vCard into an NDEF record"
$NDEFTOOL load --pack vcard.vcf tn text/vcard id '' save --force vcard.ndef

# Dump the size of the NDEF file, for information
NDEFSIZE=$(wc -c < vcard.ndef)
echo "NDEF size: $NDEFSIZE bytes"

# Read the tag
echo "Reading the tag"
CARD_INFO=$($TAGTOOL)

# Make sure the tag is writable
if [ ! "$(echo $CARD_INFO | grep -i 'NDEF Capabilities.*writeable *= *yes')" ]; then
  echo "Tag is not writeable!"
  exit -1
fi

# Make sure there's enough room on the tag for the NDEF record
TCAPA=$(echo $CARD_INFO | sed -r 's/^.*NDEF Capabilities.*capacity *= *([0-9]+) byte.*$/\1/')
if [ $TCAPA -lt $NDEFSIZE ]; then
  echo "NDEF size exceeds the tag's capacity ($TCAPA bytes)!"
  exit -1
fi

# Ask the user if they really want to write the vCard on the tag
read -p "Write NDEF onto the tag [Y/N]? " YN
if [ "$YN" != "y" ] && [ "$YN" != "Y" ]; then
  echo "Aborting"
  exit 0
fi

# Write the NDEF file onto the tag
echo "Writing NDEF onto the tag"
$TAGTOOL load vcard.ndef

echo "All done!"

Sorry, Linux-only. I don’t have Windows.

1 Like

That IS impressive, Amal doesn’t even look like a Guess Who™ character!

Nice work @Rosco

Yeah I’m properly impressed by the quality for a half kilobyte image.

I didn’t think cellphones read anything other than JPEG and GIF for contact photos. But then I remembered Android is Google, and WEBP is also Google. I figured it would make sense for them to have Android support it. So I tried, and sure enough… tada!

1 Like