diff options
| author | Xe Iaso <me@xeiaso.net> | 2024-05-28 10:08:04 -0400 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2024-05-28 10:09:26 -0400 |
| commit | 479407203e6d6654974a4bd340f576cb0b167f55 (patch) | |
| tree | d3c78f9ad4fee1ab1e51b735218764be27ce799f /internal | |
| parent | 79d43f5be33f97b36402ec8de7db51e36b1e7f45 (diff) | |
| download | x-479407203e6d6654974a4bd340f576cb0b167f55.tar.xz x-479407203e6d6654974a4bd340f576cb0b167f55.zip | |
internal/avif: remove this package
Replace this with a bunch of smaller packages that embed WebAssembly
libraries for the following functionality:
* AVIF encoding
* JXL encoding
* WEBP encoding
Also include HEIC decoding and add JXL encoding support to uploud/xedn
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/avif/COPYING | 121 | ||||
| -rw-r--r-- | internal/avif/README.md | 117 | ||||
| -rw-r--r-- | internal/avif/av1.c | 215 | ||||
| -rw-r--r-- | internal/avif/av1.h | 44 | ||||
| -rw-r--r-- | internal/avif/avif.go | 201 | ||||
| -rw-r--r-- | internal/avif/example_test.go | 42 | ||||
| -rw-r--r-- | internal/avif/hacker-nest.avif | bin | 22522 -> 0 bytes | |||
| -rw-r--r-- | internal/avif/mp4.go | 799 |
8 files changed, 0 insertions, 1539 deletions
diff --git a/internal/avif/COPYING b/internal/avif/COPYING deleted file mode 100644 index 0e259d4..0000000 --- a/internal/avif/COPYING +++ /dev/null @@ -1,121 +0,0 @@ -Creative Commons Legal Code - -CC0 1.0 Universal - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. diff --git a/internal/avif/README.md b/internal/avif/README.md deleted file mode 100644 index 9c9fef6..0000000 --- a/internal/avif/README.md +++ /dev/null @@ -1,117 +0,0 @@ -# go-avif [](https://travis-ci.org/Kagami/go-avif) [](https://godoc.org/github.com/Kagami/go-avif) - -go-avif implements -AVIF ([AV1 Still Image File Format](https://aomediacodec.github.io/av1-avif/)) -encoder for Go using libaom, the [high quality](https://github.com/Kagami/av1-bench) -AV1 codec. - -## Requirements - -Make sure libaom is installed. On typical Linux distro just run: - -#### Debian (and derivatives): -```bash -sudo apt-get install libaom-dev -``` - -#### RHEL (and derivatives): -```bash -sudo dnf install libaom-devel -``` - -## Usage - -To use go-avif in your Go code: - -```go -import "github.com/Kagami/go-avif" -``` - -To install go-avif in your $GOPATH: - -```bash -go get github.com/Kagami/go-avif -``` - -For further details see [GoDoc documentation](https://godoc.org/github.com/Kagami/go-avif). - -## Example - -```go -package main - -import ( - "image" - _ "image/jpeg" - "log" - "os" - - "github.com/Kagami/go-avif" -) - -func main() { - if len(os.Args) != 3 { - log.Fatalf("Usage: %s src.jpg dst.avif", os.Args[0]) - } - - srcPath := os.Args[1] - src, err := os.Open(srcPath) - if err != nil { - log.Fatalf("Can't open sorce file: %v", err) - } - - dstPath := os.Args[2] - dst, err := os.Create(dstPath) - if err != nil { - log.Fatalf("Can't create destination file: %v", err) - } - - img, _, err := image.Decode(src) - if err != nil { - log.Fatalf("Can't decode source file: %v", err) - } - - err = avif.Encode(dst, img, nil) - if err != nil { - log.Fatalf("Can't encode source image: %v", err) - } - - log.Printf("Encoded AVIF at %s", dstPath) -} -``` - -## CLI - -go-avif comes with handy CLI utility `avif`. It supports encoding of JPEG and -PNG files to AVIF: - -```bash -# Compile and put avif binary to $GOPATH/bin -go get github.com/Kagami/go-avif/... - -# Encode JPEG to AVIF with default settings -avif -e cat.jpg -o kitty.avif - -# Encode PNG with slowest speed -avif -e dog.png -o doggy.avif --best -q 15 - -# Lossless encoding -avif -e pig.png -o piggy.avif --lossless - -# Show help -avif -h -``` - -Static 64-bit builds for Windows, macOS and Linux are available at -[releases page](https://github.com/Kagami/go-avif/releases). They include -latest libaom from git at the moment of build. - -## Display - -To display resulting AVIF files take a look at software listed -[here](https://github.com/AOMediaCodec/av1-avif/wiki#demuxers--players). E.g. -use [avif.js](https://kagami.github.io/avif.js/) web viewer. - -## License - -go-avif is licensed under [CC0](COPYING). diff --git a/internal/avif/av1.c b/internal/avif/av1.c deleted file mode 100644 index 24cbeb8..0000000 --- a/internal/avif/av1.c +++ /dev/null @@ -1,215 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include <assert.h> -#include <aom/aom_encoder.h> -#include <aom/aomcx.h> -#include "av1.h" - -#define SET_CODEC_CONTROL(ctrl, val) \ - {if (aom_codec_control(ctx, ctrl, val)) return AVIF_ERROR_CODEC_INIT;} - -typedef struct { - aom_img_fmt_t fmt; - int dst_c_dec_h; - int dst_c_dec_v; - int bps; - int bytes_per_sample; -} avif_format; - -static avif_format convert_subsampling(const avif_subsampling subsampling) { - avif_format fmt = { 0 }; - switch (subsampling) { - case AVIF_SUBSAMPLING_I420: - fmt.fmt = AOM_IMG_FMT_I420; - fmt.dst_c_dec_h = 2; - fmt.dst_c_dec_v = 2; - fmt.bps = 12; - fmt.bytes_per_sample = 1; - break; - default: - assert(0); - } - return fmt; -} - -// We don't use aom_img_wrap() because it forces padding for odd picture -// sizes (c) libaom/common/y4minput.c -static void convert_frame(const avif_frame *frame, aom_image_t *aom_frame) { - memset(aom_frame, 0, sizeof(*aom_frame)); - avif_format fmt = convert_subsampling(frame->subsampling); - aom_frame->fmt = fmt.fmt; - aom_frame->w = aom_frame->d_w = frame->width; - aom_frame->h = aom_frame->d_h = frame->height; - aom_frame->x_chroma_shift = fmt.dst_c_dec_h >> 1; - aom_frame->y_chroma_shift = fmt.dst_c_dec_v >> 1; - aom_frame->bps = fmt.bps; - int pic_sz = frame->width * frame->height * fmt.bytes_per_sample; - int c_w = (frame->width + fmt.dst_c_dec_h - 1) / fmt.dst_c_dec_h; - c_w *= fmt.bytes_per_sample; - int c_h = (frame->height + fmt.dst_c_dec_v - 1) / fmt.dst_c_dec_v; - int c_sz = c_w * c_h; - aom_frame->stride[AOM_PLANE_Y] = frame->width * fmt.bytes_per_sample; - aom_frame->stride[AOM_PLANE_U] = aom_frame->stride[AOM_PLANE_V] = c_w; - aom_frame->planes[AOM_PLANE_Y] = frame->data; - aom_frame->planes[AOM_PLANE_U] = frame->data + pic_sz; - aom_frame->planes[AOM_PLANE_V] = frame->data + pic_sz + c_sz; -} - -static int get_frame_stats(aom_codec_ctx_t *ctx, - const aom_image_t *frame, - aom_fixed_buf_t *stats) { - if (aom_codec_encode(ctx, frame, 1/*pts*/, 1/*duration*/, 0/*flags*/)) - return AVIF_ERROR_FRAME_ENCODE; - - const aom_codec_cx_pkt_t *pkt = NULL; - aom_codec_iter_t iter = NULL; - int got_pkts = 0; - while ((pkt = aom_codec_get_cx_data(ctx, &iter)) != NULL) { - got_pkts = 1; - if (pkt->kind == AOM_CODEC_STATS_PKT) { - const uint8_t *const pkt_buf = pkt->data.twopass_stats.buf; - const size_t pkt_size = pkt->data.twopass_stats.sz; - stats->buf = realloc(stats->buf, stats->sz + pkt_size); - memcpy((uint8_t *)stats->buf + stats->sz, pkt_buf, pkt_size); - stats->sz += pkt_size; - } - } - return got_pkts; -} - -static int encode_frame(aom_codec_ctx_t *ctx, - const aom_image_t *frame, - avif_buffer *obu) { - if (aom_codec_encode(ctx, frame, 1/*pts*/, 1/*duration*/, 0/*flags*/)) - return AVIF_ERROR_FRAME_ENCODE; - - const aom_codec_cx_pkt_t *pkt = NULL; - aom_codec_iter_t iter = NULL; - int got_pkts = 0; - while ((pkt = aom_codec_get_cx_data(ctx, &iter)) != NULL) { - got_pkts = 1; - if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) { - const uint8_t *const pkt_buf = pkt->data.frame.buf; - const size_t pkt_size = pkt->data.frame.sz; - obu->buf = realloc(obu->buf, obu->sz + pkt_size); - memcpy((uint8_t *)obu->buf + obu->sz, pkt_buf, pkt_size); - obu->sz += pkt_size; - } - } - return got_pkts; -} - -static avif_error init_codec(aom_codec_iface_t *iface, - aom_codec_ctx_t *ctx, - const aom_codec_enc_cfg_t *aom_cfg, - const avif_config *cfg) { - if (aom_codec_enc_init(ctx, iface, aom_cfg, 0)) - return AVIF_ERROR_CODEC_INIT; - - SET_CODEC_CONTROL(AOME_SET_CPUUSED, cfg->speed) - SET_CODEC_CONTROL(AOME_SET_CQ_LEVEL, cfg->quality) - if (cfg->quality == 0) { - SET_CODEC_CONTROL(AV1E_SET_LOSSLESS, 1) - } - SET_CODEC_CONTROL(AV1E_SET_FRAME_PARALLEL_DECODING, 0) - SET_CODEC_CONTROL(AV1E_SET_TILE_COLUMNS, 1) - SET_CODEC_CONTROL(AV1E_SET_TILE_ROWS, 1) -#ifdef AOM_CTRL_AV1E_SET_ROW_MT - SET_CODEC_CONTROL(AV1E_SET_ROW_MT, 1) -#endif - - return AVIF_OK; -} - -static avif_error do_pass1(aom_codec_ctx_t *ctx, - const aom_image_t *frame, - aom_fixed_buf_t *stats) { - avif_error res = AVIF_OK; - - // Calculate frame statistics. - if ((res = get_frame_stats(ctx, frame, stats)) < 0) - goto fail; - - // Flush encoder. - while ((res = get_frame_stats(ctx, NULL, stats)) > 0) - continue; - -fail: - return res < 0 ? res : AVIF_OK; -} - -static avif_error do_pass2(aom_codec_ctx_t *ctx, - const aom_image_t *frame, - avif_buffer *obu) { - avif_error res = AVIF_OK; - - // Encode frame. - if ((res = encode_frame(ctx, frame, obu)) < 0) - goto fail; - - // Flush encoder. - while ((res = encode_frame(ctx, NULL, obu)) > 0) - continue; - -fail: - return res < 0 ? res : AVIF_OK; -} - -avif_error avif_encode_frame(const avif_config *cfg, - const avif_frame *frame, - avif_buffer *obu) { - // Validation. - assert(cfg->threads >= 1); - assert(cfg->speed >= AVIF_MIN_SPEED && cfg->speed <= AVIF_MAX_SPEED); - assert(cfg->quality >= AVIF_MIN_QUALITY && cfg->quality <= AVIF_MAX_QUALITY); - assert(frame->width && frame->height); - - // Prepare image. - aom_image_t aom_frame; - convert_frame(frame, &aom_frame); - - // Setup codec. - avif_error res = AVIF_OK; - aom_codec_ctx_t codec; - aom_fixed_buf_t stats = { NULL, 0 }; - aom_codec_iface_t *iface = aom_codec_av1_cx(); - aom_codec_enc_cfg_t aom_cfg; - if (aom_codec_enc_config_default(iface, &aom_cfg, 0)) { - res = AVIF_ERROR_CODEC_INIT; - goto fail; - } - aom_cfg.g_limit = 1; - aom_cfg.g_w = frame->width; - aom_cfg.g_h = frame->height; - aom_cfg.g_timebase.num = 1; - aom_cfg.g_timebase.den = 24; - aom_cfg.rc_end_usage = AOM_Q; - aom_cfg.g_threads = cfg->threads; - - // Pass 1. - aom_cfg.g_pass = AOM_RC_FIRST_PASS; - if ((res = init_codec(iface, &codec, &aom_cfg, cfg))) - goto fail; - if ((res = do_pass1(&codec, &aom_frame, &stats))) - goto fail; - if (aom_codec_destroy(&codec)) { - res = AVIF_ERROR_CODEC_DESTROY; - goto fail; - } - - // Pass 2. - aom_cfg.g_pass = AOM_RC_LAST_PASS; - aom_cfg.rc_twopass_stats_in = stats; - if ((res = init_codec(iface, &codec, &aom_cfg, cfg))) - goto fail; - if ((res = do_pass2(&codec, &aom_frame, obu))) - goto fail; - if (aom_codec_destroy(&codec)) { - res = AVIF_ERROR_CODEC_DESTROY; - goto fail; - } - -fail: - free(stats.buf); - return res; -} diff --git a/internal/avif/av1.h b/internal/avif/av1.h deleted file mode 100644 index d92e198..0000000 --- a/internal/avif/av1.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include <stdint.h> - -enum { - AVIF_MIN_SPEED = 0, - AVIF_MAX_SPEED = 8, - AVIF_MIN_QUALITY = 0, - AVIF_MAX_QUALITY = 63, -}; - -typedef enum { - AVIF_OK = 0, - AVIF_ERROR_GENERAL = -1000, - AVIF_ERROR_CODEC_INIT, - AVIF_ERROR_CODEC_DESTROY, - AVIF_ERROR_FRAME_ENCODE, -} avif_error; - -typedef enum { - AVIF_SUBSAMPLING_I420, -} avif_subsampling; - -typedef struct { - int threads; - int speed; - int quality; -} avif_config; - -typedef struct { - uint16_t width; - uint16_t height; - avif_subsampling subsampling; - uint8_t *data; -} avif_frame; - -typedef struct { - void *buf; - size_t sz; -} avif_buffer; - -avif_error avif_encode_frame(const avif_config *cfg, - const avif_frame *frame, - avif_buffer *obu); diff --git a/internal/avif/avif.go b/internal/avif/avif.go deleted file mode 100644 index 5975685..0000000 --- a/internal/avif/avif.go +++ /dev/null @@ -1,201 +0,0 @@ -// Package avif implements a AVIF image encoder. -// -// The AVIF specification is at https://aomediacodec.github.io/av1-avif/. -package avif - -// #cgo CFLAGS: -Wall -O2 -DNDEBUG -// #cgo pkg-config: aom -// #include <stdlib.h> -// #include "av1.h" -import "C" -import ( - "fmt" - "image" - "io" - "runtime" -) - -// Encoder constants. -const ( - MinThreads = 1 - MaxThreads = 64 - MinSpeed = 0 - MaxSpeed = 8 - MinQuality = 0 - MaxQuality = 63 -) - -// Options are the encoding parameters. Threads ranges from MinThreads -// to MaxThreads, 0 means use all available cores. Speed ranges from -// MinSpeed to MaxSpeed. Quality ranges from MinQuality to MaxQuality, -// lower is better, 0 means lossless encoding. SubsampleRatio specifies -// subsampling of the encoded image, nil means 4:2:0. -type Options struct { - Threads int - Speed int - Quality int - SubsampleRatio *image.YCbCrSubsampleRatio -} - -// DefaultOptions defines default encoder config. -var DefaultOptions = Options{ - Threads: 0, - Speed: 4, - Quality: 25, - SubsampleRatio: nil, -} - -// An OptionsError reports that the passed options are not valid. -type OptionsError string - -func (e OptionsError) Error() string { - return fmt.Sprintf("options error: %s", string(e)) -} - -// An EncoderError reports that the encoder error has occured. -type EncoderError int - -func (e EncoderError) ToString() string { - switch e { - case C.AVIF_ERROR_GENERAL: - return "general error" - case C.AVIF_ERROR_CODEC_INIT: - return "codec init error" - case C.AVIF_ERROR_CODEC_DESTROY: - return "codec destroy error" - case C.AVIF_ERROR_FRAME_ENCODE: - return "frame encode error" - default: - return "unknown error" - } -} - -func (e EncoderError) Error() string { - return fmt.Sprintf("encoder error: %s", e.ToString()) -} - -// A MuxerError reports that the muxer error has occured. -type MuxerError string - -func (e MuxerError) Error() string { - return fmt.Sprintf("muxer error: %s", string(e)) -} - -// RGB to BT.709 YCbCr limited range. -// https://web.archive.org/web/20180421030430/http://www.equasys.de/colorconversion.html -// TODO(Kagami): Use fixed point, don't calc chroma values for skipped pixels. -func rgb2yuv(r16, g16, b16 uint32) (uint8, uint8, uint8) { - r, g, b := float32(r16)/256, float32(g16)/256, float32(b16)/256 - y := 0.183*r + 0.614*g + 0.062*b + 16 - cb := -0.101*r - 0.339*g + 0.439*b + 128 - cr := 0.439*r - 0.399*g - 0.040*b + 128 - return uint8(y), uint8(cb), uint8(cr) -} - -// Encode writes the Image m to w in AVIF format with the given options. -// Default parameters are used if a nil *Options is passed. -// -// NOTE: Image pixels are converted to RGBA first using standard Go -// library. This is no-op for PNG images and does the right thing for -// JPEG since they are normally stored as BT.601 full range with some -// chroma subsampling. Then pixels are converted to BT.709 limited range -// with specified chroma subsampling. -// -// Alpha channel and monochrome are not supported at the moment. Only -// 4:2:0 8-bit images are supported at the moment. -func Encode(w io.Writer, m image.Image, o *Options) error { - // TODO(Kagami): More subsamplings, 10/12 bitdepth, monochrome, alpha. - // TODO(Kagami): Allow to pass BT.709 YCbCr without extra conversions. - if o == nil { - o2 := DefaultOptions - o = &o2 - } else { - o2 := *o - o = &o2 - } - if o.Threads == 0 { - o.Threads = runtime.NumCPU() - if o.Threads > MaxThreads { - o.Threads = MaxThreads - } - } - if o.SubsampleRatio == nil { - s := image.YCbCrSubsampleRatio420 - o.SubsampleRatio = &s - // if yuvImg, ok := m.(*image.YCbCr); ok { - // o.SubsampleRatio = &yuvImg.SubsampleRatio - // } - } - if o.Threads < MinThreads || o.Threads > MaxThreads { - return OptionsError("bad threads number") - } - if o.Speed < MinSpeed || o.Speed > MaxSpeed { - return OptionsError("bad speed value") - } - if o.Quality < MinQuality || o.Quality > MaxQuality { - return OptionsError("bad quality value") - } - if *o.SubsampleRatio != image.YCbCrSubsampleRatio420 { - return OptionsError("unsupported subsampling") - } - if m.Bounds().Empty() { - return OptionsError("empty image") - } - - rec := m.Bounds() - width := rec.Max.X - rec.Min.X - height := rec.Max.Y - rec.Min.Y - ySize := width * height - uSize := ((width + 1) / 2) * ((height + 1) / 2) - dataSize := ySize + uSize*2 - // Can't pass normal slice inside a struct, see - // https://github.com/golang/go/issues/14210 - dataPtr := C.malloc(C.size_t(dataSize)) - defer C.free(dataPtr) - data := (*[1 << 30]byte)(dataPtr)[:dataSize:dataSize] - - yPos := 0 - uPos := ySize - for j := rec.Min.Y; j < rec.Max.Y; j++ { - for i := rec.Min.X; i < rec.Max.X; i++ { - r16, g16, b16, _ := m.At(i, j).RGBA() - y, u, v := rgb2yuv(r16, g16, b16) - data[yPos] = y - yPos++ - // TODO(Kagami): Resample chroma planes with some better filter. - if (i-rec.Min.X)&1 == 0 && (j-rec.Min.Y)&1 == 0 { - data[uPos] = u - data[uPos+uSize] = v - uPos++ - } - } - } - - cfg := C.avif_config{ - threads: C.int(o.Threads), - speed: C.int(o.Speed), - quality: C.int(o.Quality), - } - frame := C.avif_frame{ - width: C.uint16_t(width), - height: C.uint16_t(height), - subsampling: C.AVIF_SUBSAMPLING_I420, - data: (*C.uint8_t)(dataPtr), - } - obu := C.avif_buffer{ - buf: nil, - sz: 0, - } - defer C.free(obu.buf) - // TODO(Kagami): Error description. - if eErr := C.avif_encode_frame(&cfg, &frame, &obu); eErr != 0 { - return EncoderError(eErr) - } - - obuData := (*[1 << 30]byte)(obu.buf)[:obu.sz:obu.sz] - if mErr := muxFrame(w, m, *o.SubsampleRatio, obuData); mErr != nil { - return MuxerError(mErr.Error()) - } - - return nil -} diff --git a/internal/avif/example_test.go b/internal/avif/example_test.go deleted file mode 100644 index 235cbde..0000000 --- a/internal/avif/example_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package avif_test - -import ( - "image" - _ "image/jpeg" - "log" - "os" - - "within.website/x/internal/avif" -) - -const usageHelp = "Usage: %s src.jpg dst.avif" - -func Example() { - if len(os.Args) != 3 { - log.Fatalf(usageHelp, os.Args[0]) - } - - srcPath := os.Args[1] - src, err := os.Open(srcPath) - if err != nil { - log.Fatalf("Can't open sorce file: %v", err) - } - - dstPath := os.Args[2] - dst, err := os.Create(dstPath) - if err != nil { - log.Fatalf("Can't create destination file: %v", err) - } - - img, _, err := image.Decode(src) - if err != nil { - log.Fatalf("Can't decode source file: %v", err) - } - - err = avif.Encode(dst, img, nil) - if err != nil { - log.Fatalf("Can't encode source image: %v", err) - } - - log.Printf("Encoded AVIF at %s", dstPath) -} diff --git a/internal/avif/hacker-nest.avif b/internal/avif/hacker-nest.avif Binary files differdeleted file mode 100644 index b927f01..0000000 --- a/internal/avif/hacker-nest.avif +++ /dev/null diff --git a/internal/avif/mp4.go b/internal/avif/mp4.go deleted file mode 100644 index bfed208..0000000 --- a/internal/avif/mp4.go +++ /dev/null @@ -1,799 +0,0 @@ -package avif - -import ( - "encoding/binary" - "image" - "io" -) - -type fourCC [4]byte - -var ( - boxTypeFTYP = fourCC{'f', 't', 'y', 'p'} - boxTypeMDAT = fourCC{'m', 'd', 'a', 't'} - boxTypeMETA = fourCC{'m', 'e', 't', 'a'} - boxTypeHDLR = fourCC{'h', 'd', 'l', 'r'} - boxTypePITM = fourCC{'p', 'i', 't', 'm'} - boxTypeILOC = fourCC{'i', 'l', 'o', 'c'} - boxTypeIINF = fourCC{'i', 'i', 'n', 'f'} - boxTypeINFE = fourCC{'i', 'n', 'f', 'e'} - boxTypeIPRP = fourCC{'i', 'p', 'r', 'p'} - boxTypeIPCO = fourCC{'i', 'p', 'c', 'o'} - boxTypeISPE = fourCC{'i', 's', 'p', 'e'} - boxTypePASP = fourCC{'p', 'a', 's', 'p'} - boxTypeAV1C = fourCC{'a', 'v', '1', 'C'} - boxTypePIXI = fourCC{'p', 'i', 'x', 'i'} - boxTypeIPMA = fourCC{'i', 'p', 'm', 'a'} - - itemTypeMIF1 = fourCC{'m', 'i', 'f', '1'} - itemTypeAVIF = fourCC{'a', 'v', 'i', 'f'} - itemTypeMIAF = fourCC{'m', 'i', 'a', 'f'} - itemTypePICT = fourCC{'p', 'i', 'c', 't'} - itemTypeMIME = fourCC{'m', 'i', 'm', 'e'} - itemTypeURI = fourCC{'u', 'r', 'i', ' '} - itemTypeAV01 = fourCC{'a', 'v', '0', '1'} -) - -func ulen(s string) uint32 { - return uint32(len(s)) -} - -func bflag(b bool, pos uint8) uint8 { - if b { - return 1 << (pos - 1) - } else { - return 0 - } -} - -func writeAll(w io.Writer, writers ...io.WriterTo) (err error) { - for _, wt := range writers { - _, err = wt.WriteTo(w) - if err != nil { - return - } - } - return -} - -func writeBE(w io.Writer, chunks ...interface{}) (err error) { - for _, v := range chunks { - err = binary.Write(w, binary.BigEndian, v) - if err != nil { - return - } - } - return -} - -//---------------------------------------------------------------------- - -type box struct { - size uint32 - typ fourCC -} - -func (b *box) Size() uint32 { - return 8 -} - -func (b *box) WriteTo(w io.Writer) (n int64, err error) { - err = writeBE(w, b.size, b.typ) - return -} - -//---------------------------------------------------------------------- - -type fullBox struct { - box - version uint8 - flags uint32 -} - -func (b *fullBox) Size() uint32 { - return 12 -} - -func (b *fullBox) WriteTo(w io.Writer) (n int64, err error) { - if _, err = b.box.WriteTo(w); err != nil { - return - } - versionAndFlags := (uint32(b.version) << 24) | (b.flags & 0xffffff) - err = writeBE(w, versionAndFlags) - return -} - -//---------------------------------------------------------------------- - -// File Type Box -type boxFTYP struct { - box - majorBrand fourCC - minorVersion uint32 - compatibleBrands []fourCC -} - -func (b *boxFTYP) Size() uint32 { - return b.box.Size() + - 4 /*major_brand*/ + 4 /*minor_version*/ + uint32(len(b.compatibleBrands))*4 -} - -func (b *boxFTYP) WriteTo(w io.Writer) (n int64, err error) { - b.size = b.Size() - b.typ = boxTypeFTYP - if _, err = b.box.WriteTo(w); err != nil { - return - } - err = writeBE(w, b.majorBrand, b.minorVersion, b.compatibleBrands) - return -} - -//---------------------------------------------------------------------- - -// Media Data Box -type boxMDAT struct { - box - data []byte -} - -func (b *boxMDAT) Size() uint32 { - return b.box.Size() + uint32(len(b.data)) -} - -func (b *boxMDAT) WriteTo(w io.Writer) (n int64, err error) { - b.size = b.Size() - b.typ = boxTypeMDAT - if _, err = b.box.WriteTo(w); err != nil { - return - } - _, err = w.Write(b.data) - return -} - -//---------------------------------------------------------------------- - -// The Meta box -type boxMETA struct { - fullBox - theHandler boxHDLR - primaryResource boxPITM - itemLocations boxILOC - itemInfos boxIINF - itemProps boxIPRP -} - -func (b *boxMETA) Size() uint32 { - return b.fullBox.Size() + b.theHandler.Size() + b.primaryResource.Size() + - b.itemLocations.Size() + b.itemInfos.Size() + b.itemProps.Size() -} - -func (b *boxMETA) WriteTo(w io.Writer) (n int64, err error) { - b.size = b.Size() - b.typ = boxTypeMETA - if _, err = b.fullBox.WriteTo(w); err != nil { - return - } - err = writeAll(w, &b.theHandler, &b.primaryResource, &b.itemLocations, - &b.itemInfos, &b.itemProps) - return -} - -//---------------------------------------------------------------------- - -// Handler Reference Box -type boxHDLR struct { - fullBox - preDefined uint32 - handlerType fourCC - reserved [3]uint32 - name string -} - -func (b *boxHDLR) Size() uint32 { - return b.fullBox.Size() + - 4 /*pre_defined*/ + 4 /*handler_type*/ + 12 /*reserved*/ + - ulen(b.name) + 1 /*\0*/ -} - -func (b *boxHDLR) WriteTo(w io.Writer) (n int64, err error) { - b.size = b.Size() - b.typ = boxTypeHDLR - if _, err = b.fullBox.WriteTo(w); err != nil { - return - } - err = writeBE(w, b.preDefined, b.handlerType, b.reserved, []byte(b.name), []byte{0}) - return -} - -//---------------------------------------------------------------------- - -// Primary Item Box -type boxPITM struct { - fullBox - itemID uint16 -} - -func (b *boxPITM) Size() uint32 { - return b.fullBox.Size() + 2 /*item_ID*/ -} - -func (b *boxPITM) WriteTo(w io.Writer) (n int64, err error) { - b.size = b.Size() - b.typ = boxTypePITM - if _, err = b.fullBox.WriteTo(w); err != nil { - return - } - err = writeBE(w, b.itemID) - return -} - -//---------------------------------------------------------------------- - -// The Item Location Box -type boxILOC struct { - fullBox - offsetSize uint8 // 4 bits - lengthSize uint8 // 4 bits - baseOffsetSize uint8 // 4 bits - reserved uint8 // 4 bits - itemCount uint16 - items []boxILOCItem -} - -func (b *boxILOC) Size() uint32 { - size := b.fullBox.Size() + 1 /*offset_size + length_size*/ + - 1 /*base_offset_size + reserved*/ + 2 /*item_count*/ - for _, i := range b.items { - size += 2 /*item_ID*/ + 2 /*data_reference_index*/ + uint32(b.baseOffsetSize) + - 2 /*extent_count*/ + uint32(len(i.extents))*uint32(b.offsetSize+b.lengthSize) - } - return size -} - -func (b *boxILOC) WriteTo(w io.Writer) (n int64, err error) { - b.size = b.Size() - b.typ = boxTypeILOC - b.itemCount = uint16(len(b.items)) - if _, err = b.fullBox.WriteTo(w); err != nil { - return - } - offsetSizeAndLengthSize := (b.offsetSize << 4) | (b.lengthSize & 0xf) - baseOffsetSizeAndReserved := (b.baseOffsetSize << 4) | (b.reserved & 0xf) - err = writeBE(w, offsetSizeAndLengthSize, baseOffsetSizeAndReserved, b.itemCount) - if err != nil { - return - } - for _, i := range b.items { - err = i.write(w, b.baseOffsetSize, b.offsetSize, b.lengthSize) - if err != nil { - return - } - } - return -} - -type boxILOCItem struct { - itemID uint16 - dataReferenceIndex uint16 - baseOffset uint64 // 0, 32 or 64 bits - extentCount uint16 - extents []boxILOCItemExtent |
