Friday, November 6, 2015

Hyperfocal distance with cropped image cheatsheet

TLDR: Hyperfocal distance is proportional to enlargement.

What happens to the hyperfocal distance if you intend to print a crop at the same size for the same viewing distance?


Back to basic assumptions:
  • Visual acuity of 5lp/mm at 25cm viewing distance. This is equivalent to a final print circle of confusion of 0.2mm.
  • Print size is 8x12. Enlargement = 12"/36mm = 8.47. Circle of confusion = 0.2mm/8.47 = 0.0236mm.
  • If you crop and print at the same size, you increase enlargement and decrease the circle of confusion.

Hyperfocal distance is inversely proportional to circle of confusion and is proportional to enlargement.

Example:

35mm full frame fixed focal length camera
f8.0 aperture
8x12 print size

Hyperfocal distance = 35 * 35 / 8 / 0.0236 = 6.5m.
With 1.5x crop (simulated 52mm), hyperfocal distance = 9.7m.
With 2x crop (simulated 70mm), hyperfocal distance = 13m.

Caveat:

Viewing distance is traditionally supposed to depend on the focal length (to get the same perspective), but these days, everything is viewed at phone distance :)

Thursday, November 5, 2015

Fix Whatsapp picture capture time

Whatsapp strips all EXIF info from any pictures sent through it. Without any EXIF info, Lightroom uses the file modification time as the capture time. If for some reason, the file modification time is wrong, then Lightroom gets confused. Luckily, Whatsapp puts the date in the filename. But Lightroom doesn't know about it.

So, here's a little Go program that changes the file modification time of files to the date in the filenames. Since the exact time is unknown, it is set to 3am local time.

Public domain.

UPDATE 2017/01/10: In addition to updating the file timestamp, also runs "exiftool" to update the EXIF dates in the jpg file. Overwrites the original jpeg file.

Use at your own risk!

package main

import (
        "flag"
        "fmt"
        "os"
        "os/exec"
        "regexp"
        "strconv"
        "time"
)

var trial = flag.Bool("n", true, "trail run")

func main() {
        flag.Parse()
        for _, f := range flag.Args() {
                fix(f)
        }
}

var re = regexp.MustCompile(`^([^-]*-)([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])(-WA.*)$`)

func fix(f string) {
        match := re.FindStringSubmatch(f)
        if len(match) > 0 {
                y, _ := strconv.Atoi(match[2])
                m, _ := strconv.Atoi(match[3])
                d, _ := strconv.Atoi(match[4])
                update(f, y, m, d)
        }
}

func update(f string, y int, m int, d int) {
        s, err := os.Stat(f)
        if err != nil {
                fmt.Printf("Unable to stat file %v\n", f)
                return
        }
        oldy, oldm, oldd := s.ModTime().Date()
        oldhh, oldmm, oldss := s.ModTime().Clock()
        hh, mm, ss := 3, 3, 3
        if y != oldy || time.Month(m) != oldm || d != oldd ||
                oldhh != hh || oldmm != mm || oldss != ss {
                t := time.Date(y, time.Month(m), d, hh, mm, ss, 0, time.Local)
                fmt.Printf("%v: %v -> %v\n", f, s.ModTime(), t)
                if !*trial {
                        err := os.Chtimes(f, time.Now(), t)
                        if err != nil {
                                fmt.Printf("Failed to change time on %v\n", f)
                                return
                        }
                        err = exec.Command("exiftool", "-overwrite_original_in_place", "-FileModifyDate>AllDates", f).Run()
                        if err != nil {
                                fmt.Printf("Failed to update exif of %v\n", f)
                                return
                        }
                        err = os.Chtimes(f, time.Now(), t)
                        if err != nil {
                                fmt.Printf("Failed to change time on %v\n", f)
                                return
                        }
                }
        }
}