// Определяем — это серое изображение? func isgray(im *gd.Image) bool { for x := im.Sx() - 1; x >= 0; x-- { for y := im.Sy() - 1; y >= 0; y-- { c := im.ColorsForIndex(im.ColorAt(x, y)) if c["red"] != c["green"] || c["red"] != c["blue"] { return false } } } return true }
func main() { options := parseoptions() // Показываем силу му-у-у-у-у? if v, ok := options["moo"]; ok && v == "1" { moo() } if runtime.GOMAXPROCS(-1) > 1 { runtime.GOMAXPROCS(3) } // Преобразование маски файлов в более традиционный для Go формат regexp, _ := r.Compile(`(\{[^\}]+\})`) oMask := regexp.ReplaceAllStringFunc(options["mask"], func(m string) string { return "[" + s.Join(s.Split(m[1:len(m)-1], ","), "") + "]" }) // Первоначальное значение out-dir wd, _ := os.Getwd() oOutDir := path.Clean(path.Join(wd, options["out-dir"])) // Составляем список файлов, по которому будем двигаться var oFileList []string if options["recursive"] == "1" { if path.IsAbs(options["out-dir"]) { oOutDir = path.Clean(options["out-dir"]) } oFileList = getrecurlist(oMask, oOutDir) } else { oFileList, _ = fp.Glob(oMask) } // Создаём директорий для результата, если он нужен if options["keep-name"] == "0" && !fileexists(oOutDir) { os.MkdirAll(oOutDir, 0777) } // Сколько файлов получилось? oLen := len(oFileList) if oLen < 1 { os.Stdout.WriteString("Файлы не найдены\n") os.Exit(1) } // Маска для нового имени now := time.Now().Format("2006.01.02") oNameMask := path.Join(options["out-dir"], now) if oLen > 1 { prec := strconv.Itoa(len(strconv.Itoa(oLen))) oNameMask += ".%0" + prec + "d.jpg" fmt.Printf("Found %d JPEG files.\n", oLen) } else { oNameMask += ".jpg" fmt.Println("Found 1 JPEG file.") } // Нормализация background, должны получиться три hex числа // в Go очень примитивные regexp :( if re := r.MustCompile(`^#[0-9a-fA-F]+`); len(options["background"]) == 7 && !re.MatchString(options["background"]) { options["background"] = "#ffffff" } // И переводим background компоненты oBgColor := [3]int{} for i := 1; i < len(options["background"]); i += 2 { c, _ := strconv.ParseInt(options["background"][i:i+2], 16, 0) oBgColor[i>>1] = int(c) } // Уголки для скруглений var corner *gd.Image defer corner.Destroy() coready := make(chan *gd.Image) oRadius, _ := strconv.Atoi(options["radius"]) if oRadius > 0 { go func() { oRadius, _ := strconv.Atoi(options["radius"]) // важно, что это локальная переменная, иначе будет // баг — указатель будет уже не пустой, а уголки ещё // не будут готовы corner := gd.CreateTrueColor(oRadius<<1+2, oRadius<<1+2) corner.AlphaBlending(false) corner.SaveAlpha(true) trans := corner.ColorAllocateAlpha(oBgColor[0], oBgColor[1], oBgColor[2], 127) back := corner.ColorAllocate(oBgColor[0], oBgColor[1], oBgColor[2]) corner.Fill(0, 0, trans) smoothellipse(corner, oRadius, oRadius+1, oRadius, back) // инвертируем прозрачность пикселей for x := 0; x < corner.Sx(); x++ { for y := 0; y < corner.Sy(); y++ { c := corner.ColorsForIndex(corner.ColorAt(x, y)) c["alpha"] = 127 - c["alpha"] nc := corner.ColorAllocateAlpha(c["red"], c["green"], c["blue"], c["alpha"]) corner.SetPixel(x, y, nc) } } coready <- corner }() } // Качество сохраняемой картинки oQuality, _ := strconv.Atoi(options["quality"]) // проверяем, доступен ли jpegtran // пробуем найти jpegtran oJtname, oJt := func() (string, bool) { if jpegtran != "" { if fileexists(jpegtran) { return jpegtran, true } jtname := fp.Base(jpegtran) paths := bytes.Split([]byte(os.Getenv("PATH")), []byte{fp.ListSeparator}) for _, p := range paths { if name := fp.Join(string(p), jtname); fileexists(name) { return name, true } } } return "", false }() // Временное имя для ч/б профиля oProfile := fp.Join(os.TempDir(), "cornet-bolk-bw.txt") defer os.Remove(oProfile) // Пишем профайл для ч/б изображения, профиль цветного не поддерживается «Оперой» profile, e := os.OpenFile(oProfile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0777) if e == nil { defer profile.Close() profile.WriteString("0: 0 0 0 0 ;\n0: 1 8 0 2 ;\n0: 9 63 0 2 ;\n0: 1 63 2 1 ;\n0: 1 63 1 0;") } oTmpName := fp.Join(os.TempDir(), "cornet-bolk-"+strconv.Itoa(os.Getpid())+"-") oSaved := int64(0) // Цикл обработки файлов for num, name := range oFileList { fmt.Printf("Processing %s ... ", name) im := gd.CreateFromJpeg(name) im.AlphaBlending(true) sx := im.Sx() sy := im.Sy() var w, h int // Если указана какая-то разумная ширина, то уменьшим до этой // ширины if w, _ = strconv.Atoi(options["width"]); w > 0 { h = int(float32(sy) * (float32(w) / float32(sx))) imresized := gd.CreateTrueColor(w, h) im.CopyResampled(imresized, 0, 0, 0, 0, w, h, sx, sy) im.Destroy() im = imresized } else { w, h = sx, sy } if oRadius > 0 { if corner == nil { corner = <-coready } if R := oRadius + 1; R > 1 { corner.Copy(im, 0, 0, 0, 0, R, R) corner.Copy(im, 0, h-R, 0, R, R, R) corner.Copy(im, w-R, 0, R, 0, R, R) corner.Copy(im, w-R, h-R, R, R, R, R) } } // Если имена не сохраняем, то заменяем на сгенерированное имя if options["keep-name"] == "0" { if oLen > 1 { name = fmt.Sprintf(oNameMask, num+1) } else { name = oNameMask } } // Jpegtran доступен if oJt { tmpname := oTmpName + fp.Base(name) gray := isgray(im) im.Jpeg(tmpname, oQuality) im.Destroy() im = nil // Оптимизация jpeg stat, _ := os.Stat(tmpname) cmdkeys := []string{"-copy", "none"} // Для файлов > 10КБ с вероятностью 94% лучшие результаты даёт progressive if stat.Size() > 10*1024 { cmdkeys = append(cmdkeys, "-progressive") } // Если файл серый, то оптимизируем его как серый if gray { cmdkeys = append(cmdkeys, "-grayscale", "-scans", oProfile) } cmdkeys = append(cmdkeys, "-optimize", tmpname) cmd := exec.Command(oJtname, cmdkeys...) buf := make([]byte, 2048) fp, _ := os.Create(name) out, _ := cmd.StdoutPipe() cmd.Start() for { n, _ := out.Read(buf) if n == 0 { break } fp.Write(buf[0:n]) } fp.Close() cmd.Wait() // TODO: идея такая — stdout замыкаем на Writer, берём с него данные, следим за EXIF // не забыть прочитать EXIF из файла outstat, _ := os.Stat(name) oSaved += stat.Size() - outstat.Size() os.Remove(tmpname) } else { // Jpegtran нам недоступен im.Jpeg(name, oQuality) im.Destroy() im = nil } fmt.Println("done") } if oSaved > 0 { fmt.Printf("Saved %d bytes after optimization.\n", oSaved) } }