Top Level Namespace

Defined Under Namespace

Modules: ImagePack Classes: NilClass, String

Constant Summary collapse

MOZJPEG_CORE_SOURCES =
%w[
  jcapimin.c
  jcapistd.c
  jccoefct.c
  jccolor.c
  jcdctmgr.c
  jchuff.c
  jcext.c
  jcicc.c
  jcinit.c
  jcmainct.c
  jcmarker.c
  jcmaster.c
  jcomapi.c
  jcparam.c
  jcphuff.c
  jcprepct.c
  jcsample.c
  jctrans.c
  jdapimin.c
  jdapistd.c
  jdatadst.c
  jdatasrc.c
  jdcoefct.c
  jdcolor.c
  jddctmgr.c
  jdhuff.c
  jdicc.c
  jdinput.c
  jdmainct.c
  jdmarker.c
  jdmaster.c
  jdmerge.c
  jdphuff.c
  jdpostct.c
  jdsample.c
  jdtrans.c
  jerror.c
  jfdctflt.c
  jfdctfst.c
  jfdctint.c
  jidctflt.c
  jidctfst.c
  jidctint.c
  jidctred.c
  jquant1.c
  jquant2.c
  jutils.c
  jmemmgr.c
  jmemnobs.c
].freeze
NEON_AARCH64_SOURCES =
[
  "simd/arm/aarch64/jsimd.c",
  "simd/arm/aarch64/jchuff-neon.c",
  "simd/arm/jccolor-neon.c",
  "simd/arm/jcgray-neon.c",
  "simd/arm/jcphuff-neon.c",
  "simd/arm/jcsample-neon.c",
  "simd/arm/jdcolor-neon.c",
  "simd/arm/jdmerge-neon.c",
  "simd/arm/jdsample-neon.c",
  "simd/arm/jfdctfst-neon.c",
  "simd/arm/jfdctint-neon.c",
  "simd/arm/jidctfst-neon.c",
  "simd/arm/jidctint-neon.c",
  "simd/arm/jidctred-neon.c",
  "simd/arm/jquanti-neon.c",
].freeze
X86_64_C_SOURCES =
[
  "simd/x86_64/jsimd.c",
].freeze
X86_64_ASM_SOURCES =
%w[
  simd/x86_64/jsimdcpu.asm
  simd/x86_64/jfdctflt-sse.asm
  simd/x86_64/jccolor-sse2.asm
  simd/x86_64/jcgray-sse2.asm
  simd/x86_64/jchuff-sse2.asm
  simd/x86_64/jcphuff-sse2.asm
  simd/x86_64/jcsample-sse2.asm
  simd/x86_64/jdcolor-sse2.asm
  simd/x86_64/jdmerge-sse2.asm
  simd/x86_64/jdsample-sse2.asm
  simd/x86_64/jfdctfst-sse2.asm
  simd/x86_64/jfdctint-sse2.asm
  simd/x86_64/jidctflt-sse2.asm
  simd/x86_64/jidctfst-sse2.asm
  simd/x86_64/jidctint-sse2.asm
  simd/x86_64/jidctred-sse2.asm
  simd/x86_64/jquantf-sse2.asm
  simd/x86_64/jquanti-sse2.asm
  simd/x86_64/jccolor-avx2.asm
  simd/x86_64/jcgray-avx2.asm
  simd/x86_64/jcsample-avx2.asm
  simd/x86_64/jdcolor-avx2.asm
  simd/x86_64/jdmerge-avx2.asm
  simd/x86_64/jdsample-avx2.asm
  simd/x86_64/jfdctint-avx2.asm
  simd/x86_64/jidctint-avx2.asm
  simd/x86_64/jquanti-avx2.asm
].freeze
X86_64_ASM_TEMPLATE_SOURCES =
%w[
  simd/x86_64/jccolext-sse2.asm
  simd/x86_64/jcgryext-sse2.asm
  simd/x86_64/jdcolext-sse2.asm
  simd/x86_64/jdmrgext-sse2.asm
  simd/x86_64/jccolext-avx2.asm
  simd/x86_64/jcgryext-avx2.asm
  simd/x86_64/jdcolext-avx2.asm
  simd/x86_64/jdmrgext-avx2.asm
].freeze
MOZJPEG_VERSION =
"4.1.5"
MOZJPEG_VERSION_NUMBER =
"4001005"
NEON_COMPAT_HEADER =
<<~HEADER
  /*
   * Generated by image_pack extconf.rb (replaces simd/arm/neon-compat.h.in
   * which CMake would otherwise process). All three intrinsic feature flags
   * are unconditionally enabled — every AArch64 toolchain image_pack supports
   * provides them.
   */
  #ifndef IMAGE_PACK_NEON_COMPAT_H
  #define IMAGE_PACK_NEON_COMPAT_H

  #define HAVE_VLD1_S16_X3
  #define HAVE_VLD1_U16_X2
  #define HAVE_VLD1Q_U8_X4

  #if defined(_MSC_VER) && !defined(__clang__)
  #define BUILTIN_CLZ(x)  _CountLeadingZeros(x)
  #define BUILTIN_CLZLL(x) _CountLeadingZeros64(x)
  #define BUILTIN_BSWAP64(x) _byteswap_uint64(x)
  #elif defined(__clang__) || defined(__GNUC__)
  #define BUILTIN_CLZ(x)  __builtin_clz(x)
  #define BUILTIN_CLZLL(x) __builtin_clzll(x)
  #define BUILTIN_BSWAP64(x) __builtin_bswap64(x)
  #else
  #error "Unknown compiler"
  #endif

  #endif /* IMAGE_PACK_NEON_COMPAT_H */
HEADER

Instance Method Summary collapse

Instance Method Details

#configure_vendored_mozjpeg(vendor_dir) ⇒ Object



368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'ext/image_pack/extconf.rb', line 368

def configure_vendored_mozjpeg(vendor_dir)
  versions = File.read(File.join(vendor_dir, ".vendored"))
  mozjpeg_dir = File.join(vendor_dir, "mozjpeg")

  abort "vendored MozJPEG sources not found in #{mozjpeg_dir}" unless Dir.exist?(mozjpeg_dir)

  missing_sources = MOZJPEG_CORE_SOURCES.reject { |source| File.exist?(File.join(mozjpeg_dir, source)) }
  abort "vendored MozJPEG sources are incomplete: #{missing_sources.join(', ')}" unless missing_sources.empty?

  arch = detect_simd_arch
  backend = select_simd_backend(mozjpeg_dir, arch)
  with_simd = backend[:kind] != :none
  simd_c_sources = backend.fetch(:c_sources, [])
  simd_asm_sources = backend.fetch(:asm_sources, [])
  scalar_fallback = with_simd ? [] : ["jsimd_none.c"]

  all_c_sources = MOZJPEG_CORE_SOURCES + scalar_fallback + simd_c_sources

  puts "Building with VENDORED MozJPEG from #{mozjpeg_dir}"
  puts "  #{versions.tr("\n", ", ")}"
  puts "  arch=#{arch} backend=#{backend[:kind]}"
  puts "  #{MOZJPEG_CORE_SOURCES.length} core C files + " \
       "#{simd_c_sources.length} SIMD C files + " \
       "#{simd_asm_sources.length} ASM files" \
       "#{with_simd ? '' : ' + jsimd_none.c stub'}"

  build_dir = Dir.pwd
  write_mozjpeg_config_headers!(build_dir, with_simd: with_simd)

  $CPPFLAGS += " -I#{build_dir}"
  $CPPFLAGS += " -I#{mozjpeg_dir}"

  vpath_dirs = [find_image_pack_c_dir, mozjpeg_dir]

  if with_simd
    backend[:extra_includes].each { |inc| $CPPFLAGS += " -I#{inc}" }
    extra_cflags = backend[:extra_cflags].to_s
    $CFLAGS += " #{extra_cflags}" unless extra_cflags.empty?
    simd_c_dirs = simd_c_sources.map { |rel| File.dirname(File.join(mozjpeg_dir, rel)) }.uniq
    vpath_dirs += simd_c_dirs
  end

  $srcs = ["image_pack.c"] + all_c_sources
  $VPATH = vpath_dirs.uniq

  {
    backend:       backend,
    with_simd:     with_simd,
    vpath_dirs:    vpath_dirs.uniq,
    asm_sources:   simd_asm_sources,
    mozjpeg_dir:   mozjpeg_dir,
  }
end

#detect_simd_archObject



180
181
182
183
184
185
186
# File 'ext/image_pack/extconf.rb', line 180

def detect_simd_arch
  case RUBY_PLATFORM
  when /\A(?:arm64|aarch64)/ then :neon_aarch64
  when /\A(?:x86_64|x64)/    then :x86_64
  else                            :none
  end
end

#find_image_pack_c_dirObject



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'ext/image_pack/extconf.rb', line 147

def find_image_pack_c_dir
  candidates = [
    __dir__,
    File.join(__dir__, "..", "..", "..", "..", "ext", "image_pack"),
  ]

  dir = __dir__
  6.times do
    candidates << File.join(dir, "ext", "image_pack")
    dir = File.dirname(dir)
  end

  candidates.find { |path| File.exist?(File.join(path, "image_pack.c")) }
            &.then { |path| File.expand_path(path) } || __dir__
end

#find_nasmObject



188
189
190
191
# File 'ext/image_pack/extconf.rb', line 188

def find_nasm
  ENV["AS_NASM"].presence_or_nil ||
    %w[nasm yasm].find { |bin| system("which #{bin} >/dev/null 2>&1") }
end

#find_vendor_dirObject



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'ext/image_pack/extconf.rb', line 129

def find_vendor_dir
  candidates = [
    File.join(__dir__, "vendor"),
    File.join(__dir__, "..", "..", "..", "..", "ext", "image_pack", "vendor"),
    File.expand_path("../../ext/image_pack/vendor", __dir__),
    File.join(Dir.pwd, "ext", "image_pack", "vendor"),
  ]

  dir = __dir__
  6.times do
    candidates << File.join(dir, "ext", "image_pack", "vendor")
    dir = File.dirname(dir)
  end

  candidates.find { |path| File.exist?(File.join(path, ".vendored")) }
            &.then { |path| File.expand_path(path) }
end

#inline_keywordObject



172
173
174
# File 'ext/image_pack/extconf.rb', line 172

def inline_keyword
  msvc? ? "__inline" : "inline __attribute__((always_inline))"
end

#msvc?Boolean

Returns:

  • (Boolean)


163
164
165
# File 'ext/image_pack/extconf.rb', line 163

def msvc?
  /mswin|mingw|cygwin/i.match?(RUBY_PLATFORM)
end

#patch_makefile_nasm!(asm_sources, mozjpeg_dir) ⇒ Object



433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'ext/image_pack/extconf.rb', line 433

def patch_makefile_nasm!(asm_sources, mozjpeg_dir)
  return if asm_sources.empty?

  nasm = find_nasm
  abort "NASM/YASM disappeared between probe and Makefile patch?" unless nasm

  fmt, platform_define, pic_define =
    case RUBY_PLATFORM
    when /darwin/
      ["macho64", "MACHO", nil]
    when /linux/
      ["elf64", "ELF", "PIC"]
    when /mswin|mingw|cygwin/
      ["win64", "WIN64", nil]
    else
      ["elf64", "ELF", "PIC"]
    end

  obj_ext = RbConfig::CONFIG["OBJEXT"] || "o"

  asm_objs = asm_sources.map { |rel| File.basename(rel, ".asm") + ".#{obj_ext}" }
  inc_dirs = [
    File.join(mozjpeg_dir, "simd"),
    File.join(mozjpeg_dir, "simd", "nasm"),
    File.join(mozjpeg_dir, "simd", "x86_64"),
    File.join(mozjpeg_dir, "win"),
  ].select { |d| Dir.exist?(d) }

  nasm_includes = inc_dirs.map { |d| "-I#{d}/" }.join(" ")
  arch_defines = %w[elf64 macho64 win64].include?(fmt) ? ["__x86_64__"] : []
  nasm_defines = ([platform_define, pic_define] + arch_defines).compact.map { |name| "-D#{name}" }.join(" ")

  rules = +""
  asm_sources.each do |rel|
    src = File.join(mozjpeg_dir, rel)
    obj = File.basename(rel, ".asm") + ".#{obj_ext}"
    rules << "#{obj}: #{src}\n"
    rules << "\t#{nasm} -f#{fmt} #{nasm_defines} #{nasm_includes} -o $@ $<\n"
  end

  makefile = File.read("Makefile")
  return if makefile.include?("# vendored mozjpeg nasm")

  asm_obj_line = asm_objs.join(" ")
  makefile.sub!(/^(OBJS\s*=[^\n]*)$/) { "#{Regexp.last_match(1)} #{asm_obj_line}\n# vendored mozjpeg nasm" }
  makefile << "\n# vendored mozjpeg nasm rules\n"
  makefile << rules
  File.write("Makefile", makefile)
  puts "  Patched Makefile with #{asm_sources.length} NASM rules (#{nasm}, -f#{fmt})"
end

#patch_makefile_vpath!(vpath_dirs) ⇒ Object



422
423
424
425
426
427
428
429
430
431
# File 'ext/image_pack/extconf.rb', line 422

def patch_makefile_vpath!(vpath_dirs)
  makefile = File.read("Makefile")
  return if makefile.include?("# vendored mozjpeg vpath")

  vpath_lines = vpath_dirs.map { |dir| "vpath %.c #{dir}" }.join("\n")

  makefile.sub!(/^(VPATH\s*=[^\n]*)$/) { "#{Regexp.last_match(1)}\n# vendored mozjpeg vpath\n#{vpath_lines}" }
  File.write("Makefile", makefile)
  puts "  Patched Makefile with #{vpath_dirs.length} VPATH entries"
end

#select_simd_backend(mozjpeg_dir, arch) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'ext/image_pack/extconf.rb', line 258

def select_simd_backend(mozjpeg_dir, arch)
  case arch
  when :neon_aarch64
    template_in = File.join(mozjpeg_dir, "simd", "arm", "neon-compat.h.in")
    sources_present = NEON_AARCH64_SOURCES.all? { |rel| File.exist?(File.join(mozjpeg_dir, rel)) }
    template_present = File.exist?(template_in)

    if sources_present && template_present
      write_neon_compat_header!(mozjpeg_dir)

      {
        kind:           :neon_aarch64,
        c_sources:      NEON_AARCH64_SOURCES,
        asm_sources:    [],
        extra_includes: [
          File.join(mozjpeg_dir, "simd", "arm"),
          File.join(mozjpeg_dir, "simd", "arm", "aarch64"),
        ],
        extra_cflags:   "-DNEON_INTRINSICS=1"
      }
    else
      missing = NEON_AARCH64_SOURCES.reject { |rel| File.exist?(File.join(mozjpeg_dir, rel)) }
      missing << "simd/arm/neon-compat.h.in" unless template_present
      warn "image_pack: ARM64 detected but NEON sources missing under #{mozjpeg_dir}:"
      missing.each { |m| warn "  - #{m}" }
      warn "image_pack: re-run `bundle exec rake vendor` after updating script/vendor_libs.rb."
      warn "image_pack: falling back to scalar."
      { kind: :none }
    end

  when :x86_64
    if find_nasm.nil?
      warn "image_pack: x86_64 detected but NASM/YASM not found on PATH. Falling back to scalar; install nasm for ~3-4x speedup."
      { kind: :none }
    elsif !X86_64_C_SOURCES.all? { |rel| File.exist?(File.join(mozjpeg_dir, rel)) }
      warn "image_pack: x86_64 SIMD sources missing under #{mozjpeg_dir}/simd/x86_64/. Falling back to scalar."
      { kind: :none }
    else
      {
        kind:           :x86_64_simd,
        c_sources:      X86_64_C_SOURCES,
        asm_sources:    X86_64_ASM_SOURCES.select { |rel| File.exist?(File.join(mozjpeg_dir, rel)) },
        extra_includes: [
          File.join(mozjpeg_dir, "simd"),
          File.join(mozjpeg_dir, "simd", "nasm"),
          File.join(mozjpeg_dir, "simd", "x86_64"),
        ],
        extra_cflags:   "",
      }
    end

  else
    { kind: :none }
  end
end

#sizeof_size_tObject



167
168
169
170
# File 'ext/image_pack/extconf.rb', line 167

def sizeof_size_t
  value = RbConfig::CONFIG["SIZEOF_SIZE_T"] || RbConfig::CONFIG["SIZEOF_VOIDP"] || RbConfig::CONFIG["SIZEOF_LONG"]
  value.to_i.positive? ? value.to_i : 8
end

#thread_local_keywordObject



176
177
178
# File 'ext/image_pack/extconf.rb', line 176

def thread_local_keyword
  msvc? ? "__declspec(thread)" : "__thread"
end

#write_mozjpeg_config_headers!(build_dir, with_simd:) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'ext/image_pack/extconf.rb', line 193

def write_mozjpeg_config_headers!(build_dir, with_simd:)
  with_simd_flag = with_simd ? "#define WITH_SIMD 1" : "#undef WITH_SIMD"

  File.write(File.join(build_dir, "jconfig.h"), <<~HEADER)
    /* Generated by image_pack extconf.rb. */
    #define JPEG_LIB_VERSION 62
    #define LIBJPEG_TURBO_VERSION #{MOZJPEG_VERSION}
    #define LIBJPEG_TURBO_VERSION_NUMBER #{MOZJPEG_VERSION_NUMBER}
    #undef C_ARITH_CODING_SUPPORTED
    #undef D_ARITH_CODING_SUPPORTED
    #define MEM_SRCDST_SUPPORTED 1
    #{with_simd_flag}
    #define BITS_IN_JSAMPLE 8
    #undef RIGHT_SHIFT_IS_UNSIGNED
  HEADER

  File.write(File.join(build_dir, "jconfigint.h"), <<~HEADER)
    /* Generated by image_pack extconf.rb. */
    #define BUILD "image_pack"
    #undef inline
    #define INLINE #{inline_keyword}
    #define THREAD_LOCAL #{thread_local_keyword}
    #define PACKAGE_NAME "mozjpeg"
    #define VERSION "#{MOZJPEG_VERSION}"
    #define SIZEOF_SIZE_T #{sizeof_size_t}
    #if defined(__has_attribute)
    #if __has_attribute(fallthrough)
    #define FALLTHROUGH __attribute__((fallthrough));
    #else
    #define FALLTHROUGH
    #endif
    #else
    #define FALLTHROUGH
    #endif
  HEADER

  File.write(File.join(build_dir, "jversion.h"), <<~HEADER)
    /* Generated by image_pack extconf.rb. */
    #if JPEG_LIB_VERSION >= 80
    #define JVERSION "8d  15-Jan-2012"
    #elif JPEG_LIB_VERSION >= 70
    #define JVERSION "7  27-Jun-2009"
    #else
    #define JVERSION "6b  27-Mar-1998"
    #endif

    #define JCOPYRIGHT \
      "Copyright (C) 2009-2023 D. R. Commander\\n" \
      "Copyright (C) 2015, 2020 Google, Inc.\\n" \
      "Copyright (C) 2019-2020 Arm Limited\\n" \
      "Copyright (C) 2015-2016, 2018 Matthieu Darbois\\n" \
      "Copyright (C) 2011-2016 Siarhei Siamashka\\n" \
      "Copyright (C) 2015 Intel Corporation\\n" \
      "Copyright (C) 2013-2014 Linaro Limited\\n" \
      "Copyright (C) 2013-2014 MIPS Technologies, Inc.\\n" \
      "Copyright (C) 2009, 2012 Pierre Ossman for Cendio AB\\n" \
      "Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies)\\n" \
      "Copyright (C) 1999-2006 MIYASAKA Masaru\\n" \
      "Copyright (C) 1991-2020 Thomas G. Lane, Guido Vollbeding"

    #define JCOPYRIGHT_SHORT \
      "Copyright (C) 1991-2023 The libjpeg-turbo Project and many others"
  HEADER
end

#write_neon_compat_header!(mozjpeg_dir) ⇒ Object



343
344
345
346
347
348
349
350
351
# File 'ext/image_pack/extconf.rb', line 343

def write_neon_compat_header!(mozjpeg_dir)
  target = File.join(mozjpeg_dir, "simd", "arm", "neon-compat.h")
  return target if File.exist?(target) &&
                   File.read(target) == NEON_COMPAT_HEADER
  FileUtils.mkdir_p(File.dirname(target))
  File.write(target, NEON_COMPAT_HEADER)
  puts "  Generated #{target}"
  target
end