Allocate avifReformatState tables dynamically

Instead of a fixed-length array that may be larger than necessary and
uses 1<<12 * 2 = 8kB of stack memory which may be limited on some
environments.
diff --git a/include/avif/internal.h b/include/avif/internal.h
index 31bf425..db163ca 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -135,10 +135,6 @@
 
     avifPixelFormatInfo formatInfo;
 
-    // LUTs for going from YUV limited/full unorm -> full range RGB FP32
-    float unormFloatTableY[1 << 12];
-    float unormFloatTableUV[1 << 12];
-
     avifReformatMode mode;
     // Used by avifImageYUVToRGB() only. avifImageRGBToYUV() uses a local variable (alphaMode) instead.
     avifAlphaMultiplyMode toRGBAlphaMode;
diff --git a/src/reformat.c b/src/reformat.c
index 2200e8c..5ec6ee8 100644
--- a/src/reformat.c
+++ b/src/reformat.c
@@ -135,20 +135,6 @@
     state->rangeY = (float)((state->yuvRange == AVIF_RANGE_LIMITED) ? (219 << (state->yuvDepth - 8)) : state->yuvMaxChannel);
     state->rangeUV = (float)((state->yuvRange == AVIF_RANGE_LIMITED) ? (224 << (state->yuvDepth - 8)) : state->yuvMaxChannel);
 
-    uint32_t cpCount = 1 << image->depth;
-    if (state->mode == AVIF_REFORMAT_MODE_IDENTITY) {
-        for (uint32_t cp = 0; cp < cpCount; ++cp) {
-            state->unormFloatTableY[cp] = ((float)cp - state->biasY) / state->rangeY;
-            state->unormFloatTableUV[cp] = ((float)cp - state->biasY) / state->rangeY;
-        }
-    } else {
-        for (uint32_t cp = 0; cp < cpCount; ++cp) {
-            // Review this when implementing YCgCo limited range support.
-            state->unormFloatTableY[cp] = ((float)cp - state->biasY) / state->rangeY;
-            state->unormFloatTableUV[cp] = ((float)cp - state->biasUV) / state->rangeUV;
-        }
-    }
-
     state->toRGBAlphaMode = AVIF_ALPHA_MULTIPLY_MODE_NO_OP;
     if (image->alphaPlane) {
         if (!avifRGBFormatHasAlpha(rgb->format) || rgb->ignoreAlpha) {
@@ -461,6 +447,52 @@
     return AVIF_RESULT_OK;
 }
 
+// Allocates and fills look-up tables for going from YUV limited/full unorm -> full range RGB FP32.
+static avifBool avifCreateYUVToRGBLookUpTables(float ** unormFloatTableY, float ** unormFloatTableUV, int depth, const avifReformatState * state)
+{
+    const size_t cpCount = (size_t)1 << depth;
+
+    assert(unormFloatTableY);
+    *unormFloatTableY = avifAlloc(cpCount * sizeof(**unormFloatTableY));
+    AVIF_CHECK(*unormFloatTableY);
+    for (uint32_t cp = 0; cp < cpCount; ++cp) {
+        (*unormFloatTableY)[cp] = ((float)cp - state->biasY) / state->rangeY;
+    }
+
+    if (unormFloatTableUV) {
+        if (state->mode == AVIF_REFORMAT_MODE_IDENTITY) {
+            // Just reuse the luma table since the chroma values are the same.
+            *unormFloatTableUV = *unormFloatTableY;
+        } else {
+            *unormFloatTableUV = avifAlloc(cpCount * sizeof(**unormFloatTableUV));
+            if (!*unormFloatTableUV) {
+                avifFree(*unormFloatTableY);
+                *unormFloatTableY = NULL;
+                return AVIF_FALSE;
+            }
+            for (uint32_t cp = 0; cp < cpCount; ++cp) {
+                // Review this when implementing YCgCo limited range support.
+                (*unormFloatTableUV)[cp] = ((float)cp - state->biasUV) / state->rangeUV;
+            }
+        }
+    }
+    return AVIF_TRUE;
+}
+
+// Frees look-up tables allocated with avifCreateYUVToRGBLookUpTables().
+static void avifFreeYUVToRGBLookUpTables(float ** unormFloatTableY, float ** unormFloatTableUV)
+{
+    if (unormFloatTableUV) {
+        if (*unormFloatTableUV != *unormFloatTableY) {
+            avifFree(*unormFloatTableUV);
+        }
+        *unormFloatTableUV = NULL;
+    }
+
+    avifFree(*unormFloatTableY);
+    *unormFloatTableY = NULL;
+}
+
 #define RGB565(R, G, B) ((uint16_t)(((B) >> 3) | (((G) >> 2) << 5) | (((R) >> 3) << 11)))
 
 static void avifStoreRGB8Pixel(avifRGBFormat format, uint8_t R, uint8_t G, uint8_t B, uint8_t * ptrR, uint8_t * ptrG, uint8_t * ptrB)
@@ -484,8 +516,9 @@
     const float kr = state->kr;
     const float kg = state->kg;
     const float kb = state->kb;
-    const float * const unormFloatTableY = state->unormFloatTableY;
-    const float * const unormFloatTableUV = state->unormFloatTableUV;
+    float * unormFloatTableY = NULL;
+    float * unormFloatTableUV = NULL;
+    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
     const uint32_t yuvChannelBytes = state->yuvChannelBytes;
     const uint32_t rgbPixelBytes = state->rgbPixelBytes;
 
@@ -747,6 +780,7 @@
             ptrB += rgbPixelBytes;
         }
     }
+    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV);
     return AVIF_RESULT_OK;
 }
 
@@ -756,8 +790,9 @@
     const float kg = state->kg;
     const float kb = state->kb;
     const uint32_t rgbPixelBytes = state->rgbPixelBytes;
-    const float * const unormFloatTableY = state->unormFloatTableY;
-    const float * const unormFloatTableUV = state->unormFloatTableUV;
+    float * unormFloatTableY = NULL;
+    float * unormFloatTableUV = NULL;
+    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
 
     const uint16_t yuvMaxChannel = (uint16_t)state->yuvMaxChannel;
     const float rgbMaxChannelF = state->rgbMaxChannelF;
@@ -799,6 +834,7 @@
             ptrB += rgbPixelBytes;
         }
     }
+    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV);
     return AVIF_RESULT_OK;
 }
 
@@ -808,7 +844,8 @@
     const float kg = state->kg;
     const float kb = state->kb;
     const uint32_t rgbPixelBytes = state->rgbPixelBytes;
-    const float * const unormFloatTableY = state->unormFloatTableY;
+    float * unormFloatTableY = NULL;
+    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, NULL, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
 
     const uint16_t yuvMaxChannel = (uint16_t)state->yuvMaxChannel;
     const float rgbMaxChannelF = state->rgbMaxChannelF;
@@ -843,6 +880,7 @@
             ptrB += rgbPixelBytes;
         }
     }
+    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, NULL);
     return AVIF_RESULT_OK;
 }
 
@@ -852,8 +890,9 @@
     const float kg = state->kg;
     const float kb = state->kb;
     const uint32_t rgbPixelBytes = state->rgbPixelBytes;
-    const float * const unormFloatTableY = state->unormFloatTableY;
-    const float * const unormFloatTableUV = state->unormFloatTableUV;
+    float * unormFloatTableY = NULL;
+    float * unormFloatTableUV = NULL;
+    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
 
     const uint16_t yuvMaxChannel = (uint16_t)state->yuvMaxChannel;
     const float rgbMaxChannelF = state->rgbMaxChannelF;
@@ -899,6 +938,7 @@
             ptrB += rgbPixelBytes;
         }
     }
+    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV);
     return AVIF_RESULT_OK;
 }
 
@@ -908,7 +948,8 @@
     const float kg = state->kg;
     const float kb = state->kb;
     const uint32_t rgbPixelBytes = state->rgbPixelBytes;
-    const float * const unormFloatTableY = state->unormFloatTableY;
+    float * unormFloatTableY = NULL;
+    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, NULL, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
 
     const uint16_t yuvMaxChannel = (uint16_t)state->yuvMaxChannel;
     const float rgbMaxChannelF = state->rgbMaxChannelF;
@@ -947,6 +988,7 @@
             ptrB += rgbPixelBytes;
         }
     }
+    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, NULL);
     return AVIF_RESULT_OK;
 }
 
@@ -956,8 +998,9 @@
     const float kg = state->kg;
     const float kb = state->kb;
     const uint32_t rgbPixelBytes = state->rgbPixelBytes;
-    const float * const unormFloatTableY = state->unormFloatTableY;
-    const float * const unormFloatTableUV = state->unormFloatTableUV;
+    float * unormFloatTableY = NULL;
+    float * unormFloatTableUV = NULL;
+    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
 
     const float rgbMaxChannelF = state->rgbMaxChannelF;
     for (uint32_t j = 0; j < image->height; ++j) {
@@ -993,6 +1036,7 @@
             ptrB += rgbPixelBytes;
         }
     }
+    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV);
     return AVIF_RESULT_OK;
 }
 
@@ -1002,7 +1046,8 @@
     const float kg = state->kg;
     const float kb = state->kb;
     const uint32_t rgbPixelBytes = state->rgbPixelBytes;
-    const float * const unormFloatTableY = state->unormFloatTableY;
+    float * unormFloatTableY = NULL;
+    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, NULL, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
 
     const float rgbMaxChannelF = state->rgbMaxChannelF;
     for (uint32_t j = 0; j < image->height; ++j) {
@@ -1033,6 +1078,7 @@
             ptrB += rgbPixelBytes;
         }
     }
+    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, NULL);
     return AVIF_RESULT_OK;
 }
 
@@ -1075,8 +1121,9 @@
     const float kg = state->kg;
     const float kb = state->kb;
     const uint32_t rgbPixelBytes = state->rgbPixelBytes;
-    const float * const unormFloatTableY = state->unormFloatTableY;
-    const float * const unormFloatTableUV = state->unormFloatTableUV;
+    float * unormFloatTableY = NULL;
+    float * unormFloatTableUV = NULL;
+    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
 
     const float rgbMaxChannelF = state->rgbMaxChannelF;
     for (uint32_t j = 0; j < image->height; ++j) {
@@ -1116,6 +1163,7 @@
             ptrB += rgbPixelBytes;
         }
     }
+    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV);
     return AVIF_RESULT_OK;
 }
 
@@ -1125,7 +1173,8 @@
     const float kg = state->kg;
     const float kb = state->kb;
     const uint32_t rgbPixelBytes = state->rgbPixelBytes;
-    const float * const unormFloatTableY = state->unormFloatTableY;
+    float * unormFloatTableY = NULL;
+    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, NULL, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
 
     const float rgbMaxChannelF = state->rgbMaxChannelF;
     for (uint32_t j = 0; j < image->height; ++j) {
@@ -1160,6 +1209,7 @@
             ptrB += rgbPixelBytes;
         }
     }
+    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, NULL);
     return AVIF_RESULT_OK;
 }