File size: 28,972 Bytes
d46f4a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
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
257
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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
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
421
422
423
424
425
426
427
428
429
430
431
432
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
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
package com.mojang.blaze3d.platform;

import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.jtracy.MemoryPool;
import com.mojang.jtracy.TracyClient;
import com.mojang.logging.LogUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.function.IntUnaryOperator;
import javax.annotation.Nullable;
import net.minecraft.client.gui.font.providers.FreeTypeUtil;
import net.minecraft.util.ARGB;
import net.minecraft.util.PngInfo;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.commons.io.IOUtils;
import org.lwjgl.stb.STBIWriteCallback;
import org.lwjgl.stb.STBImage;
import org.lwjgl.stb.STBImageResize;
import org.lwjgl.stb.STBImageWrite;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.freetype.FT_Bitmap;
import org.lwjgl.util.freetype.FT_Face;
import org.lwjgl.util.freetype.FT_GlyphSlot;
import org.lwjgl.util.freetype.FreeType;
import org.slf4j.Logger;

@OnlyIn(Dist.CLIENT)
public final class NativeImage implements AutoCloseable {
    private static final Logger LOGGER = LogUtils.getLogger();
    private static final MemoryPool MEMORY_POOL = TracyClient.createMemoryPool("NativeImage");
    private static final Set<StandardOpenOption> OPEN_OPTIONS = EnumSet.of(
        StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
    );
    private final NativeImage.Format format;
    private final int width;
    private final int height;
    private final boolean useStbFree;
    private long pixels;
    private final long size;

    public NativeImage(int p_84968_, int p_84969_, boolean p_84970_) {
        this(NativeImage.Format.RGBA, p_84968_, p_84969_, p_84970_);
    }

    public NativeImage(NativeImage.Format p_84972_, int p_84973_, int p_84974_, boolean p_84975_) {
        if (p_84973_ > 0 && p_84974_ > 0) {
            this.format = p_84972_;
            this.width = p_84973_;
            this.height = p_84974_;
            this.size = (long)p_84973_ * (long)p_84974_ * (long)p_84972_.components();
            this.useStbFree = false;
            if (p_84975_) {
                this.pixels = MemoryUtil.nmemCalloc(1L, this.size);
            } else {
                this.pixels = MemoryUtil.nmemAlloc(this.size);
            }

            MEMORY_POOL.malloc(this.pixels, (int)this.size);
            if (this.pixels == 0L) {
                throw new IllegalStateException("Unable to allocate texture of size " + p_84973_ + "x" + p_84974_ + " (" + p_84972_.components() + " channels)");
            }
        } else {
            throw new IllegalArgumentException("Invalid texture size: " + p_84973_ + "x" + p_84974_);
        }
    }

    private NativeImage(NativeImage.Format p_84977_, int p_84978_, int p_84979_, boolean p_84980_, long p_84981_) {
        if (p_84978_ > 0 && p_84979_ > 0) {
            this.format = p_84977_;
            this.width = p_84978_;
            this.height = p_84979_;
            this.useStbFree = p_84980_;
            this.pixels = p_84981_;
            this.size = (long)p_84978_ * (long)p_84979_ * (long)p_84977_.components();
        } else {
            throw new IllegalArgumentException("Invalid texture size: " + p_84978_ + "x" + p_84979_);
        }
    }

    @Override
    public String toString() {
        return "NativeImage[" + this.format + " " + this.width + "x" + this.height + "@" + this.pixels + (this.useStbFree ? "S" : "N") + "]";
    }

    private boolean isOutsideBounds(int p_166423_, int p_166424_) {
        return p_166423_ < 0 || p_166423_ >= this.width || p_166424_ < 0 || p_166424_ >= this.height;
    }

    public static NativeImage read(InputStream p_85059_) throws IOException {
        return read(NativeImage.Format.RGBA, p_85059_);
    }

    public static NativeImage read(@Nullable NativeImage.Format p_85049_, InputStream p_85050_) throws IOException {
        ByteBuffer bytebuffer = null;

        NativeImage nativeimage;
        try {
            bytebuffer = TextureUtil.readResource(p_85050_);
            bytebuffer.rewind();
            nativeimage = read(p_85049_, bytebuffer);
        } finally {
            MemoryUtil.memFree(bytebuffer);
            IOUtils.closeQuietly(p_85050_);
        }

        return nativeimage;
    }

    public static NativeImage read(ByteBuffer p_85063_) throws IOException {
        return read(NativeImage.Format.RGBA, p_85063_);
    }

    public static NativeImage read(byte[] p_273041_) throws IOException {
        MemoryStack memorystack = MemoryStack.stackGet();
        int i = memorystack.getPointer();
        if (i < p_273041_.length) {
            ByteBuffer bytebuffer1 = MemoryUtil.memAlloc(p_273041_.length);

            NativeImage nativeimage1;
            try {
                nativeimage1 = putAndRead(bytebuffer1, p_273041_);
            } finally {
                MemoryUtil.memFree(bytebuffer1);
            }

            return nativeimage1;
        } else {
            NativeImage nativeimage;
            try (MemoryStack memorystack1 = MemoryStack.stackPush()) {
                ByteBuffer bytebuffer = memorystack1.malloc(p_273041_.length);
                nativeimage = putAndRead(bytebuffer, p_273041_);
            }

            return nativeimage;
        }
    }

    private static NativeImage putAndRead(ByteBuffer p_378245_, byte[] p_377207_) throws IOException {
        p_378245_.put(p_377207_);
        p_378245_.rewind();
        return read(p_378245_);
    }

    public static NativeImage read(@Nullable NativeImage.Format p_85052_, ByteBuffer p_85053_) throws IOException {
        if (p_85052_ != null && !p_85052_.supportedByStb()) {
            throw new UnsupportedOperationException("Don't know how to read format " + p_85052_);
        } else if (MemoryUtil.memAddress(p_85053_) == 0L) {
            throw new IllegalArgumentException("Invalid buffer");
        } else {
            PngInfo.validateHeader(p_85053_);

            NativeImage nativeimage;
            try (MemoryStack memorystack = MemoryStack.stackPush()) {
                IntBuffer intbuffer = memorystack.mallocInt(1);
                IntBuffer intbuffer1 = memorystack.mallocInt(1);
                IntBuffer intbuffer2 = memorystack.mallocInt(1);
                ByteBuffer bytebuffer = STBImage.stbi_load_from_memory(p_85053_, intbuffer, intbuffer1, intbuffer2, p_85052_ == null ? 0 : p_85052_.components);
                if (bytebuffer == null) {
                    throw new IOException("Could not load image: " + STBImage.stbi_failure_reason());
                }

                long i = MemoryUtil.memAddress(bytebuffer);
                MEMORY_POOL.malloc(i, bytebuffer.limit());
                nativeimage = new NativeImage(
                    p_85052_ == null ? NativeImage.Format.getStbFormat(intbuffer2.get(0)) : p_85052_, intbuffer.get(0), intbuffer1.get(0), true, i
                );
            }

            return nativeimage;
        }
    }

    private void checkAllocated() {
        if (this.pixels == 0L) {
            throw new IllegalStateException("Image is not allocated.");
        }
    }

    @Override
    public void close() {
        if (this.pixels != 0L) {
            if (this.useStbFree) {
                STBImage.nstbi_image_free(this.pixels);
            } else {
                MemoryUtil.nmemFree(this.pixels);
            }

            MEMORY_POOL.free(this.pixels);
        }

        this.pixels = 0L;
    }

    public int getWidth() {
        return this.width;
    }

    public int getHeight() {
        return this.height;
    }

    public NativeImage.Format format() {
        return this.format;
    }

    private int getPixelABGR(int p_366605_, int p_368577_) {
        if (this.format != NativeImage.Format.RGBA) {
            throw new IllegalArgumentException(String.format(Locale.ROOT, "getPixelRGBA only works on RGBA images; have %s", this.format));
        } else if (this.isOutsideBounds(p_366605_, p_368577_)) {
            throw new IllegalArgumentException(
                String.format(Locale.ROOT, "(%s, %s) outside of image bounds (%s, %s)", p_366605_, p_368577_, this.width, this.height)
            );
        } else {
            this.checkAllocated();
            long i = ((long)p_366605_ + (long)p_368577_ * (long)this.width) * 4L;
            return MemoryUtil.memGetInt(this.pixels + i);
        }
    }

    public int getPixel(int p_364178_, int p_364265_) {
        return ARGB.fromABGR(this.getPixelABGR(p_364178_, p_364265_));
    }

    private void setPixelABGR(int p_366486_, int p_360988_, int p_364498_) {
        if (this.format != NativeImage.Format.RGBA) {
            throw new IllegalArgumentException(String.format(Locale.ROOT, "setPixelRGBA only works on RGBA images; have %s", this.format));
        } else if (this.isOutsideBounds(p_366486_, p_360988_)) {
            throw new IllegalArgumentException(
                String.format(Locale.ROOT, "(%s, %s) outside of image bounds (%s, %s)", p_366486_, p_360988_, this.width, this.height)
            );
        } else {
            this.checkAllocated();
            long i = ((long)p_366486_ + (long)p_360988_ * (long)this.width) * 4L;
            MemoryUtil.memPutInt(this.pixels + i, p_364498_);
        }
    }

    public void setPixel(int p_364494_, int p_368505_, int p_361991_) {
        this.setPixelABGR(p_364494_, p_368505_, ARGB.toABGR(p_361991_));
    }

    public NativeImage mappedCopy(IntUnaryOperator p_267084_) {
        if (this.format != NativeImage.Format.RGBA) {
            throw new IllegalArgumentException(String.format(Locale.ROOT, "function application only works on RGBA images; have %s", this.format));
        } else {
            this.checkAllocated();
            NativeImage nativeimage = new NativeImage(this.width, this.height, false);
            int i = this.width * this.height;
            IntBuffer intbuffer = MemoryUtil.memIntBuffer(this.pixels, i);
            IntBuffer intbuffer1 = MemoryUtil.memIntBuffer(nativeimage.pixels, i);

            for (int j = 0; j < i; j++) {
                int k = ARGB.fromABGR(intbuffer.get(j));
                int l = p_267084_.applyAsInt(k);
                intbuffer1.put(j, ARGB.toABGR(l));
            }

            return nativeimage;
        }
    }

    public void applyToAllPixels(IntUnaryOperator p_285490_) {
        if (this.format != NativeImage.Format.RGBA) {
            throw new IllegalArgumentException(String.format(Locale.ROOT, "function application only works on RGBA images; have %s", this.format));
        } else {
            this.checkAllocated();
            int i = this.width * this.height;
            IntBuffer intbuffer = MemoryUtil.memIntBuffer(this.pixels, i);

            for (int j = 0; j < i; j++) {
                int k = ARGB.fromABGR(intbuffer.get(j));
                int l = p_285490_.applyAsInt(k);
                intbuffer.put(j, ARGB.toABGR(l));
            }
        }
    }

    public int[] getPixelsABGR() {
        if (this.format != NativeImage.Format.RGBA) {
            throw new IllegalArgumentException(String.format(Locale.ROOT, "getPixels only works on RGBA images; have %s", this.format));
        } else {
            this.checkAllocated();
            int[] aint = new int[this.width * this.height];
            MemoryUtil.memIntBuffer(this.pixels, this.width * this.height).get(aint);
            return aint;
        }
    }

    public int[] getPixels() {
        int[] aint = this.getPixelsABGR();

        for (int i = 0; i < aint.length; i++) {
            aint[i] = ARGB.fromABGR(aint[i]);
        }

        return aint;
    }

    public byte getLuminanceOrAlpha(int p_85088_, int p_85089_) {
        if (!this.format.hasLuminanceOrAlpha()) {
            throw new IllegalArgumentException(String.format(Locale.ROOT, "no luminance or alpha in %s", this.format));
        } else if (this.isOutsideBounds(p_85088_, p_85089_)) {
            throw new IllegalArgumentException(
                String.format(Locale.ROOT, "(%s, %s) outside of image bounds (%s, %s)", p_85088_, p_85089_, this.width, this.height)
            );
        } else {
            int i = (p_85088_ + p_85089_ * this.width) * this.format.components() + this.format.luminanceOrAlphaOffset() / 8;
            return MemoryUtil.memGetByte(this.pixels + (long)i);
        }
    }

    @Deprecated
    public int[] makePixelArray() {
        if (this.format != NativeImage.Format.RGBA) {
            throw new UnsupportedOperationException("can only call makePixelArray for RGBA images.");
        } else {
            this.checkAllocated();
            int[] aint = new int[this.getWidth() * this.getHeight()];

            for (int i = 0; i < this.getHeight(); i++) {
                for (int j = 0; j < this.getWidth(); j++) {
                    aint[j + i * this.getWidth()] = this.getPixel(j, i);
                }
            }

            return aint;
        }
    }

    public void upload(int p_85041_, int p_85042_, int p_85043_, boolean p_85044_) {
        this.upload(p_85041_, p_85042_, p_85043_, 0, 0, this.width, this.height, p_85044_);
    }

    public void upload(int p_85004_, int p_85005_, int p_85006_, int p_85007_, int p_85008_, int p_85009_, int p_85010_, boolean p_85011_) {
        if (!RenderSystem.isOnRenderThreadOrInit()) {
            RenderSystem.recordRenderCall(() -> this._upload(p_85004_, p_85005_, p_85006_, p_85007_, p_85008_, p_85009_, p_85010_, p_85011_));
        } else {
            this._upload(p_85004_, p_85005_, p_85006_, p_85007_, p_85008_, p_85009_, p_85010_, p_85011_);
        }
    }

    private void _upload(int p_85091_, int p_85092_, int p_85093_, int p_85094_, int p_85095_, int p_85096_, int p_85097_, boolean p_85098_) {
        try {
            RenderSystem.assertOnRenderThreadOrInit();
            this.checkAllocated();
            if (p_85096_ == this.getWidth()) {
                GlStateManager._pixelStore(3314, 0);
            } else {
                GlStateManager._pixelStore(3314, this.getWidth());
            }

            GlStateManager._pixelStore(3316, p_85094_);
            GlStateManager._pixelStore(3315, p_85095_);
            this.format.setUnpackPixelStoreState();
            GlStateManager._texSubImage2D(3553, p_85091_, p_85092_, p_85093_, p_85096_, p_85097_, this.format.glFormat(), 5121, this.pixels);
        } finally {
            if (p_85098_) {
                this.close();
            }
        }
    }

    public void downloadTexture(int p_85046_, boolean p_85047_) {
        RenderSystem.assertOnRenderThread();
        this.checkAllocated();
        this.format.setPackPixelStoreState();
        GlStateManager._getTexImage(3553, p_85046_, this.format.glFormat(), 5121, this.pixels);
        if (p_85047_ && this.format.hasAlpha()) {
            for (int i = 0; i < this.getHeight(); i++) {
                for (int j = 0; j < this.getWidth(); j++) {
                    this.setPixelABGR(j, i, this.getPixelABGR(j, i) | 255 << this.format.alphaOffset());
                }
            }
        }
    }

    public void downloadDepthBuffer(float p_166401_) {
        RenderSystem.assertOnRenderThread();
        if (this.format.components() != 1) {
            throw new IllegalStateException("Depth buffer must be stored in NativeImage with 1 component.");
        } else {
            this.checkAllocated();
            this.format.setPackPixelStoreState();
            GlStateManager._readPixels(0, 0, this.width, this.height, 6402, 5121, this.pixels);
        }
    }

    public void drawPixels() {
        RenderSystem.assertOnRenderThread();
        this.format.setUnpackPixelStoreState();
        GlStateManager._glDrawPixels(this.width, this.height, this.format.glFormat(), 5121, this.pixels);
    }

    public void writeToFile(File p_85057_) throws IOException {
        this.writeToFile(p_85057_.toPath());
    }

    public boolean copyFromFont(FT_Face p_334818_, int p_85070_) {
        if (this.format.components() != 1) {
            throw new IllegalArgumentException("Can only write fonts into 1-component images.");
        } else if (FreeTypeUtil.checkError(FreeType.FT_Load_Glyph(p_334818_, p_85070_, 4), "Loading glyph")) {
            return false;
        } else {
            FT_GlyphSlot ft_glyphslot = Objects.requireNonNull(p_334818_.glyph(), "Glyph not initialized");
            FT_Bitmap ft_bitmap = ft_glyphslot.bitmap();
            if (ft_bitmap.pixel_mode() != 2) {
                throw new IllegalStateException("Rendered glyph was not 8-bit grayscale");
            } else if (ft_bitmap.width() == this.getWidth() && ft_bitmap.rows() == this.getHeight()) {
                int i = ft_bitmap.width() * ft_bitmap.rows();
                ByteBuffer bytebuffer = Objects.requireNonNull(ft_bitmap.buffer(i), "Glyph has no bitmap");
                MemoryUtil.memCopy(MemoryUtil.memAddress(bytebuffer), this.pixels, (long)i);
                return true;
            } else {
                throw new IllegalArgumentException(
                    String.format(
                        Locale.ROOT,
                        "Glyph bitmap of size %sx%s does not match image of size: %sx%s",
                        ft_bitmap.width(),
                        ft_bitmap.rows(),
                        this.getWidth(),
                        this.getHeight()
                    )
                );
            }
        }
    }

    public void writeToFile(Path p_85067_) throws IOException {
        if (!this.format.supportedByStb()) {
            throw new UnsupportedOperationException("Don't know how to write format " + this.format);
        } else {
            this.checkAllocated();

            try (WritableByteChannel writablebytechannel = Files.newByteChannel(p_85067_, OPEN_OPTIONS)) {
                if (!this.writeToChannel(writablebytechannel)) {
                    throw new IOException("Could not write image to the PNG file \"" + p_85067_.toAbsolutePath() + "\": " + STBImage.stbi_failure_reason());
                }
            }
        }
    }

    private boolean writeToChannel(WritableByteChannel p_85065_) throws IOException {
        NativeImage.WriteCallback nativeimage$writecallback = new NativeImage.WriteCallback(p_85065_);

        boolean flag;
        try {
            int i = Math.min(this.getHeight(), Integer.MAX_VALUE / this.getWidth() / this.format.components());
            if (i < this.getHeight()) {
                LOGGER.warn("Dropping image height from {} to {} to fit the size into 32-bit signed int", this.getHeight(), i);
            }

            if (STBImageWrite.nstbi_write_png_to_func(nativeimage$writecallback.address(), 0L, this.getWidth(), i, this.format.components(), this.pixels, 0)
                != 0) {
                nativeimage$writecallback.throwIfException();
                return true;
            }

            flag = false;
        } finally {
            nativeimage$writecallback.free();
        }

        return flag;
    }

    public void copyFrom(NativeImage p_85055_) {
        if (p_85055_.format() != this.format) {
            throw new UnsupportedOperationException("Image formats don't match.");
        } else {
            int i = this.format.components();
            this.checkAllocated();
            p_85055_.checkAllocated();
            if (this.width == p_85055_.width) {
                MemoryUtil.memCopy(p_85055_.pixels, this.pixels, Math.min(this.size, p_85055_.size));
            } else {
                int j = Math.min(this.getWidth(), p_85055_.getWidth());
                int k = Math.min(this.getHeight(), p_85055_.getHeight());

                for (int l = 0; l < k; l++) {
                    int i1 = l * p_85055_.getWidth() * i;
                    int j1 = l * this.getWidth() * i;
                    MemoryUtil.memCopy(p_85055_.pixels + (long)i1, this.pixels + (long)j1, (long)j);
                }
            }
        }
    }

    public void fillRect(int p_84998_, int p_84999_, int p_85000_, int p_85001_, int p_85002_) {
        for (int i = p_84999_; i < p_84999_ + p_85001_; i++) {
            for (int j = p_84998_; j < p_84998_ + p_85000_; j++) {
                this.setPixel(j, i, p_85002_);
            }
        }
    }

    public void copyRect(int p_85026_, int p_85027_, int p_85028_, int p_85029_, int p_85030_, int p_85031_, boolean p_85032_, boolean p_85033_) {
        this.copyRect(this, p_85026_, p_85027_, p_85026_ + p_85028_, p_85027_ + p_85029_, p_85030_, p_85031_, p_85032_, p_85033_);
    }

    public void copyRect(
        NativeImage p_261644_, int p_262056_, int p_261490_, int p_261959_, int p_262110_, int p_261522_, int p_261505_, boolean p_261480_, boolean p_261622_
    ) {
        for (int i = 0; i < p_261505_; i++) {
            for (int j = 0; j < p_261522_; j++) {
                int k = p_261480_ ? p_261522_ - 1 - j : j;
                int l = p_261622_ ? p_261505_ - 1 - i : i;
                int i1 = this.getPixelABGR(p_262056_ + j, p_261490_ + i);
                p_261644_.setPixelABGR(p_261959_ + k, p_262110_ + l, i1);
            }
        }
    }

    public void flipY() {
        this.checkAllocated();
        int i = this.format.components();
        int j = this.getWidth() * i;
        long k = MemoryUtil.nmemAlloc((long)j);

        try {
            for (int l = 0; l < this.getHeight() / 2; l++) {
                int i1 = l * this.getWidth() * i;
                int j1 = (this.getHeight() - 1 - l) * this.getWidth() * i;
                MemoryUtil.memCopy(this.pixels + (long)i1, k, (long)j);
                MemoryUtil.memCopy(this.pixels + (long)j1, this.pixels + (long)i1, (long)j);
                MemoryUtil.memCopy(k, this.pixels + (long)j1, (long)j);
            }
        } finally {
            MemoryUtil.nmemFree(k);
        }
    }

    public void resizeSubRectTo(int p_85035_, int p_85036_, int p_85037_, int p_85038_, NativeImage p_85039_) {
        this.checkAllocated();
        if (p_85039_.format() != this.format) {
            throw new UnsupportedOperationException("resizeSubRectTo only works for images of the same format.");
        } else {
            int i = this.format.components();
            STBImageResize.nstbir_resize_uint8(
                this.pixels + (long)((p_85035_ + p_85036_ * this.getWidth()) * i),
                p_85037_,
                p_85038_,
                this.getWidth() * i,
                p_85039_.pixels,
                p_85039_.getWidth(),
                p_85039_.getHeight(),
                0,
                i
            );
        }
    }

    public void untrack() {
        DebugMemoryUntracker.untrack(this.pixels);
    }

    @OnlyIn(Dist.CLIENT)
    public static enum Format {
        RGBA(4, 6408, true, true, true, false, true, 0, 8, 16, 255, 24, true),
        RGB(3, 6407, true, true, true, false, false, 0, 8, 16, 255, 255, true),
        LUMINANCE_ALPHA(2, 33319, false, false, false, true, true, 255, 255, 255, 0, 8, true),
        LUMINANCE(1, 6403, false, false, false, true, false, 0, 0, 0, 0, 255, true);

        final int components;
        private final int glFormat;
        private final boolean hasRed;
        private final boolean hasGreen;
        private final boolean hasBlue;
        private final boolean hasLuminance;
        private final boolean hasAlpha;
        private final int redOffset;
        private final int greenOffset;
        private final int blueOffset;
        private final int luminanceOffset;
        private final int alphaOffset;
        private final boolean supportedByStb;

        private Format(
            final int p_85148_,
            final int p_85149_,
            final boolean p_85150_,
            final boolean p_85151_,
            final boolean p_85152_,
            final boolean p_85153_,
            final boolean p_85154_,
            final int p_85155_,
            final int p_85156_,
            final int p_85157_,
            final int p_85158_,
            final int p_85159_,
            final boolean p_85160_
        ) {
            this.components = p_85148_;
            this.glFormat = p_85149_;
            this.hasRed = p_85150_;
            this.hasGreen = p_85151_;
            this.hasBlue = p_85152_;
            this.hasLuminance = p_85153_;
            this.hasAlpha = p_85154_;
            this.redOffset = p_85155_;
            this.greenOffset = p_85156_;
            this.blueOffset = p_85157_;
            this.luminanceOffset = p_85158_;
            this.alphaOffset = p_85159_;
            this.supportedByStb = p_85160_;
        }

        public int components() {
            return this.components;
        }

        public void setPackPixelStoreState() {
            RenderSystem.assertOnRenderThread();
            GlStateManager._pixelStore(3333, this.components());
        }

        public void setUnpackPixelStoreState() {
            RenderSystem.assertOnRenderThreadOrInit();
            GlStateManager._pixelStore(3317, this.components());
        }

        public int glFormat() {
            return this.glFormat;
        }

        public boolean hasRed() {
            return this.hasRed;
        }

        public boolean hasGreen() {
            return this.hasGreen;
        }

        public boolean hasBlue() {
            return this.hasBlue;
        }

        public boolean hasLuminance() {
            return this.hasLuminance;
        }

        public boolean hasAlpha() {
            return this.hasAlpha;
        }

        public int redOffset() {
            return this.redOffset;
        }

        public int greenOffset() {
            return this.greenOffset;
        }

        public int blueOffset() {
            return this.blueOffset;
        }

        public int luminanceOffset() {
            return this.luminanceOffset;
        }

        public int alphaOffset() {
            return this.alphaOffset;
        }

        public boolean hasLuminanceOrRed() {
            return this.hasLuminance || this.hasRed;
        }

        public boolean hasLuminanceOrGreen() {
            return this.hasLuminance || this.hasGreen;
        }

        public boolean hasLuminanceOrBlue() {
            return this.hasLuminance || this.hasBlue;
        }

        public boolean hasLuminanceOrAlpha() {
            return this.hasLuminance || this.hasAlpha;
        }

        public int luminanceOrRedOffset() {
            return this.hasLuminance ? this.luminanceOffset : this.redOffset;
        }

        public int luminanceOrGreenOffset() {
            return this.hasLuminance ? this.luminanceOffset : this.greenOffset;
        }

        public int luminanceOrBlueOffset() {
            return this.hasLuminance ? this.luminanceOffset : this.blueOffset;
        }

        public int luminanceOrAlphaOffset() {
            return this.hasLuminance ? this.luminanceOffset : this.alphaOffset;
        }

        public boolean supportedByStb() {
            return this.supportedByStb;
        }

        static NativeImage.Format getStbFormat(int p_85168_) {
            switch (p_85168_) {
                case 1:
                    return LUMINANCE;
                case 2:
                    return LUMINANCE_ALPHA;
                case 3:
                    return RGB;
                case 4:
                default:
                    return RGBA;
            }
        }
    }

    @OnlyIn(Dist.CLIENT)
    public static enum InternalGlFormat {
        RGBA(6408),
        RGB(6407),
        RG(33319),
        RED(6403);

        private final int glFormat;

        private InternalGlFormat(final int p_85190_) {
            this.glFormat = p_85190_;
        }

        public int glFormat() {
            return this.glFormat;
        }
    }

    @OnlyIn(Dist.CLIENT)
    static class WriteCallback extends STBIWriteCallback {
        private final WritableByteChannel output;
        @Nullable
        private IOException exception;

        WriteCallback(WritableByteChannel p_85198_) {
            this.output = p_85198_;
        }

        @Override
        public void invoke(long p_85204_, long p_85205_, int p_85206_) {
            ByteBuffer bytebuffer = getData(p_85205_, p_85206_);

            try {
                this.output.write(bytebuffer);
            } catch (IOException ioexception) {
                this.exception = ioexception;
            }
        }

        public void throwIfException() throws IOException {
            if (this.exception != null) {
                throw this.exception;
            }
        }
    }
}