diff --git a/include/avif/internal.h b/include/avif/internal.h index ccee529955..801f6810d9 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -179,28 +179,37 @@ typedef enum avifSampleTransformTokenType // Operands. AVIF_SAMPLE_TRANSFORM_CONSTANT = 0, AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX = 1, - - // Operators. L is the left operand. R is the right operand if there are two operands. - AVIF_SAMPLE_TRANSFORM_NEGATE = 2, // S = -L - AVIF_SAMPLE_TRANSFORM_ABSOLUTE = 3, // S = |L| - AVIF_SAMPLE_TRANSFORM_SUM = 4, // S = L + R - AVIF_SAMPLE_TRANSFORM_DIFFERENCE = 5, // S = L - R - AVIF_SAMPLE_TRANSFORM_PRODUCT = 6, // S = L * R - AVIF_SAMPLE_TRANSFORM_DIVIDE = 7, // S = R==0 ? L : floor(L / R) - AVIF_SAMPLE_TRANSFORM_AND = 8, // S = L & R - AVIF_SAMPLE_TRANSFORM_OR = 9, // S = L | R - AVIF_SAMPLE_TRANSFORM_XOR = 10, // S = L ^ R - AVIF_SAMPLE_TRANSFORM_NOT = 11, // S = ~L - AVIF_SAMPLE_TRANSFORM_MSB = 12, // S = L<=0 ? 0 : floor(log2(L)) - AVIF_SAMPLE_TRANSFORM_POW = 13, // S = L==0 ? 0 : pow(L, R) - AVIF_SAMPLE_TRANSFORM_MIN = 14, // S = L<=R ? L : R - AVIF_SAMPLE_TRANSFORM_MAX = 15, // S = L<=R ? R : L - AVIF_SAMPLE_TRANSFORM_RESERVED + AVIF_SAMPLE_TRANSFORM_FIRST_INPUT_IMAGE_ITEM_INDEX = 1, + AVIF_SAMPLE_TRANSFORM_LAST_INPUT_IMAGE_ITEM_INDEX = 32, + + // Unary operators. L is the left operand. + AVIF_SAMPLE_TRANSFORM_FIRST_UNARY_OPERATOR = 64, + AVIF_SAMPLE_TRANSFORM_NEGATION = 64, // S = -L + AVIF_SAMPLE_TRANSFORM_ABSOLUTE = 65, // S = |L| + AVIF_SAMPLE_TRANSFORM_NOT = 66, // S = ~L + AVIF_SAMPLE_TRANSFORM_BSR = 67, // S = L<=0 ? 0 : floor(log2(L)) + AVIF_SAMPLE_TRANSFORM_LAST_UNARY_OPERATOR = 67, + + // Binary operators. L is the left operand. R is the right operand. + AVIF_SAMPLE_TRANSFORM_FIRST_BINARY_OPERATOR = 128, + AVIF_SAMPLE_TRANSFORM_SUM = 128, // S = L + R + AVIF_SAMPLE_TRANSFORM_DIFFERENCE = 129, // S = L - R + AVIF_SAMPLE_TRANSFORM_PRODUCT = 130, // S = L * R + AVIF_SAMPLE_TRANSFORM_QUOTIENT = 131, // S = R==0 ? L : floor(L / R) + AVIF_SAMPLE_TRANSFORM_AND = 132, // S = L & R + AVIF_SAMPLE_TRANSFORM_OR = 133, // S = L | R + AVIF_SAMPLE_TRANSFORM_XOR = 134, // S = L ^ R + AVIF_SAMPLE_TRANSFORM_POW = 135, // S = L==0 ? 0 : pow(L, R) + AVIF_SAMPLE_TRANSFORM_MIN = 136, // S = L<=R ? L : R + AVIF_SAMPLE_TRANSFORM_MAX = 137, // S = L<=R ? R : L + AVIF_SAMPLE_TRANSFORM_LAST_BINARY_OPERATOR = 137, + + AVIF_SAMPLE_TRANSFORM_RESERVED = 138 } avifSampleTransformTokenType; typedef struct avifSampleTransformToken { - uint8_t type; // avifSampleTransformTokenType + avifSampleTransformTokenType type; int32_t constant; // If type is AVIF_SAMPLE_TRANSFORM_CONSTANT. // Only 32-bit (bit_depth=2) constants are supported. uint8_t inputImageItemIndex; // If type is AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX. 1-based. diff --git a/src/read.c b/src/read.c index 4173726375..adfe738aa6 100644 --- a/src/read.c +++ b/src/read.c @@ -2115,14 +2115,24 @@ static avifResult avifParseSampleTransformTokens(avifROStream * s, avifSampleTra avifSampleTransformToken * token = (avifSampleTransformToken *)avifArrayPush(expression); AVIF_CHECKERR(token != NULL, AVIF_RESULT_OUT_OF_MEMORY); - AVIF_CHECKERR(avifROStreamRead(s, &token->type, /*size=*/1), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(8) token; - if (token->type == AVIF_SAMPLE_TRANSFORM_CONSTANT) { + uint8_t tokenValue = AVIF_SAMPLE_TRANSFORM_RESERVED; + AVIF_CHECKERR(avifROStreamRead(s, &tokenValue, /*size=*/1), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(8) token; + if (tokenValue == AVIF_SAMPLE_TRANSFORM_CONSTANT) { + token->type = AVIF_SAMPLE_TRANSFORM_CONSTANT; // Two's complement representation is assumed here. uint32_t constant; AVIF_CHECKERR(avifROStreamReadU32(s, &constant), AVIF_RESULT_BMFF_PARSE_FAILED); // signed int(1<<(bit_depth+3)) constant; - token->constant = *(int32_t *)&constant; // maybe =(int32_t)constant; is enough - } else if (token->type == AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX) { - AVIF_CHECKERR(avifROStreamRead(s, &token->inputImageItemIndex, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(8) input_image_item_index; + token->constant = (int32_t)constant; + } else if (tokenValue <= AVIF_SAMPLE_TRANSFORM_LAST_INPUT_IMAGE_ITEM_INDEX) { + AVIF_ASSERT_OR_RETURN(tokenValue >= AVIF_SAMPLE_TRANSFORM_FIRST_INPUT_IMAGE_ITEM_INDEX); + token->type = AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX; + token->inputImageItemIndex = tokenValue; + } else if (tokenValue >= AVIF_SAMPLE_TRANSFORM_FIRST_UNARY_OPERATOR && tokenValue <= AVIF_SAMPLE_TRANSFORM_LAST_UNARY_OPERATOR) { + token->type = (avifSampleTransformTokenType)tokenValue; // unary operator + } else if (tokenValue >= AVIF_SAMPLE_TRANSFORM_FIRST_BINARY_OPERATOR && tokenValue <= AVIF_SAMPLE_TRANSFORM_LAST_BINARY_OPERATOR) { + token->type = (avifSampleTransformTokenType)tokenValue; // binary operator + } else { + token->type = AVIF_SAMPLE_TRANSFORM_RESERVED; } } AVIF_CHECKERR(avifROStreamRemainingBytes(s) == 0, AVIF_RESULT_BMFF_PARSE_FAILED); @@ -2138,8 +2148,9 @@ static avifResult avifParseSampleTransformImageBox(const uint8_t * raw, { BEGIN_STREAM(s, raw, rawLen, diag, "Box[sato]"); - uint8_t version, bitDepth; - AVIF_CHECKERR(avifROStreamReadBitsU8(&s, &version, /*bitCount=*/6), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(6) version = 0; + uint8_t version, reserved, bitDepth; + AVIF_CHECKERR(avifROStreamReadBitsU8(&s, &version, /*bitCount=*/2), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(2) version = 0; + AVIF_CHECKERR(avifROStreamReadBitsU8(&s, &reserved, /*bitCount=*/4), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(4) reserved; AVIF_CHECKERR(avifROStreamReadBitsU8(&s, &bitDepth, /*bitCount=*/2), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(2) bit_depth; AVIF_CHECKERR(version == 0, AVIF_RESULT_NOT_IMPLEMENTED); AVIF_CHECKERR(bitDepth == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32, AVIF_RESULT_NOT_IMPLEMENTED); diff --git a/src/sampletransform.c b/src/sampletransform.c index 5210366b0f..2a20502adb 100644 --- a/src/sampletransform.c +++ b/src/sampletransform.c @@ -21,13 +21,15 @@ avifBool avifSampleTransformExpressionIsValid(const avifSampleTransformExpressio AVIF_CHECK(token->inputImageItemIndex != 0); AVIF_CHECK(token->inputImageItemIndex <= numInputImageItems); } - if (token->type == AVIF_SAMPLE_TRANSFORM_CONSTANT || token->type == AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX) { + if (token->type < AVIF_SAMPLE_TRANSFORM_FIRST_UNARY_OPERATOR) { + // Likely an operand. ++stackSize; - } else if (token->type == AVIF_SAMPLE_TRANSFORM_NEGATE || token->type == AVIF_SAMPLE_TRANSFORM_ABSOLUTE || - token->type == AVIF_SAMPLE_TRANSFORM_NOT || token->type == AVIF_SAMPLE_TRANSFORM_MSB) { + } else if (token->type < AVIF_SAMPLE_TRANSFORM_FIRST_BINARY_OPERATOR) { + // Likely a unary operator. AVIF_CHECK(stackSize >= 1); // Pop one and push one. } else { + // Likely a binary operator. AVIF_CHECK(stackSize >= 2); --stackSize; // Pop two and push one. } @@ -131,7 +133,7 @@ avifResult avifSampleTransformRecipeToExpression(avifSampleTransformRecipe recip // The second image represents the 4 least significant bits of the reconstructed, bit-depth-extended output image. AVIF_ASSERT_OR_RETURN(avifPushInputImageItem(expression, 2)); AVIF_ASSERT_OR_RETURN(avifPushConstant(expression, 16)); - AVIF_ASSERT_OR_RETURN(avifPushOperator(expression, AVIF_SAMPLE_TRANSFORM_DIVIDE)); + AVIF_ASSERT_OR_RETURN(avifPushOperator(expression, AVIF_SAMPLE_TRANSFORM_QUOTIENT)); } AVIF_ASSERT_OR_RETURN(avifPushOperator(expression, AVIF_SAMPLE_TRANSFORM_SUM)); return AVIF_RESULT_OK; @@ -198,13 +200,13 @@ static int32_t avifSampleTransformClamp32b(int64_t value) static int32_t avifSampleTransformOperation32bOneOperand(int32_t operand, uint8_t operator) { switch (operator) { - case AVIF_SAMPLE_TRANSFORM_NEGATE: + case AVIF_SAMPLE_TRANSFORM_NEGATION: return avifSampleTransformClamp32b(-(int64_t)operand); case AVIF_SAMPLE_TRANSFORM_ABSOLUTE: return operand >= 0 ? operand : avifSampleTransformClamp32b(-(int64_t)operand); case AVIF_SAMPLE_TRANSFORM_NOT: return ~operand; - case AVIF_SAMPLE_TRANSFORM_MSB: { + case AVIF_SAMPLE_TRANSFORM_BSR: { if (operand <= 0) { return 0; } @@ -230,7 +232,7 @@ static int32_t avifSampleTransformOperation32bTwoOperands(int32_t leftOperand, i return avifSampleTransformClamp32b(leftOperand - rightOperand); case AVIF_SAMPLE_TRANSFORM_PRODUCT: return avifSampleTransformClamp32b(leftOperand * rightOperand); - case AVIF_SAMPLE_TRANSFORM_DIVIDE: + case AVIF_SAMPLE_TRANSFORM_QUOTIENT: return rightOperand == 0 ? leftOperand : leftOperand / rightOperand; case AVIF_SAMPLE_TRANSFORM_AND: return leftOperand & rightOperand; @@ -315,8 +317,8 @@ static avifResult avifImageApplyExpression32b(avifImage * dstImage, const uint8_t * row = avifImagePlane(image, c) + avifImagePlaneRowBytes(image, c) * y; AVIF_ASSERT_OR_RETURN(stackSize < stackCapacity); stack[stackSize++] = avifImageUsesU16(image) ? ((const uint16_t *)row)[x] : row[x]; - } else if (token->type == AVIF_SAMPLE_TRANSFORM_NEGATE || token->type == AVIF_SAMPLE_TRANSFORM_ABSOLUTE || - token->type == AVIF_SAMPLE_TRANSFORM_NOT || token->type == AVIF_SAMPLE_TRANSFORM_MSB) { + } else if (token->type == AVIF_SAMPLE_TRANSFORM_NEGATION || token->type == AVIF_SAMPLE_TRANSFORM_ABSOLUTE || + token->type == AVIF_SAMPLE_TRANSFORM_NOT || token->type == AVIF_SAMPLE_TRANSFORM_BSR) { AVIF_ASSERT_OR_RETURN(stackSize >= 1); stack[stackSize - 1] = avifSampleTransformOperation32bOneOperand(stack[stackSize - 1], token->type); // Pop one and push one. diff --git a/src/write.c b/src/write.c index 14dd7c896d..c6f0649274 100644 --- a/src/write.c +++ b/src/write.c @@ -1066,19 +1066,22 @@ static avifResult avifImageCopyAltImageMetadata(avifImage * altImageMetadata, co #if defined(AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM) static avifResult avifEncoderWriteSampleTransformTokens(avifRWStream * s, const avifSampleTransformExpression * expression) { - AVIF_ASSERT_OR_RETURN(expression->count <= 256); + AVIF_ASSERT_OR_RETURN(expression->count <= 255); AVIF_CHECKRES(avifRWStreamWriteU8(s, (uint8_t)expression->count)); // unsigned int(8) token_count; for (uint32_t t = 0; t < expression->count; ++t) { const avifSampleTransformToken * token = &expression->tokens[t]; - AVIF_CHECKRES(avifRWStreamWriteU8(s, token->type)); // unsigned int(8) token; if (token->type == AVIF_SAMPLE_TRANSFORM_CONSTANT) { + AVIF_CHECKRES(avifRWStreamWriteU8(s, token->type)); // unsigned int(8) token; // TODO(yguyon): Verify two's complement representation is guaranteed here. - const uint32_t constant = *(const uint32_t *)&token->constant; + const uint32_t constant = (uint32_t)token->constant; AVIF_CHECKRES(avifRWStreamWriteU32(s, constant)); // signed int(1<<(bit_depth+3)) constant; } else if (token->type == AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX) { - AVIF_CHECKRES(avifRWStreamWriteU8(s, token->inputImageItemIndex)); // unsigned int(8) input_image_item_index; + AVIF_CHECKRES(avifRWStreamWriteU8(s, token->inputImageItemIndex)); // unsigned int(8) token; + } else { + // Operator. + AVIF_CHECKRES(avifRWStreamWriteU8(s, token->type)); // unsigned int(8) token; } } return AVIF_RESULT_OK; @@ -1088,7 +1091,8 @@ static avifResult avifEncoderWriteSampleTransformPayload(avifEncoder * encoder, { avifRWStream s; avifRWStreamStart(&s, data); - AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/6)); // unsigned int(6) version = 0; + AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/2)); // unsigned int(2) version = 0; + AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, /*bitCount=*/4)); // unsigned int(4) reserved; // AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32 is necessary because the two input images // once combined use 16-bit unsigned values, but intermediate results are stored in signed integers. AVIF_CHECKRES(avifRWStreamWriteBits(&s, AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32, /*bitCount=*/2)); // unsigned int(2) bit_depth; @@ -1450,7 +1454,7 @@ static avifResult avifEncoderCreateSatoImage(avifEncoder * encoder, if (encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B) { if (isBase) { AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 8, planes)); - AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_DIVIDE, 256, planes)); + AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_QUOTIENT, 256, planes)); } else { AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 8, planes)); AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_AND, 255, planes)); @@ -1458,7 +1462,7 @@ static avifResult avifEncoderCreateSatoImage(avifEncoder * encoder, } else if (encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_4B) { if (isBase) { AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 12, planes)); - AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_DIVIDE, 16, planes)); + AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_QUOTIENT, 16, planes)); } else { AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 8, planes)); AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_AND, 15, planes)); @@ -1487,7 +1491,7 @@ static avifResult avifEncoderCreateSatoImage(avifEncoder * encoder, AVIF_RESULT_NOT_IMPLEMENTED); if (isBase) { AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 12, planes)); - AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_DIVIDE, 16, planes)); + AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_QUOTIENT, 16, planes)); } else { AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 8, planes)); avifCodec * codec = NULL; diff --git a/tests/gtest/avif16bittest.cc b/tests/gtest/avif16bittest.cc index 7baf5464c7..337c47facd 100644 --- a/tests/gtest/avif16bittest.cc +++ b/tests/gtest/avif16bittest.cc @@ -87,7 +87,7 @@ TEST_P(SampleTransformTest, Avif16bit) { {AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX, 0, /*inputImageItemIndex=*/1}, {AVIF_SAMPLE_TRANSFORM_CONSTANT, 1 << shift, 0}, - {AVIF_SAMPLE_TRANSFORM_DIVIDE, 0, 0}}; + {AVIF_SAMPLE_TRANSFORM_QUOTIENT, 0, 0}}; ASSERT_EQ(avifImageApplyOperations( image_no_sato.get(), AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32, /*numTokens=*/3, tokens, /*numInputImageItems=*/1, diff --git a/tests/gtest/avifsampletransformtest.cc b/tests/gtest/avifsampletransformtest.cc index 79de749080..1305380194 100644 --- a/tests/gtest/avifsampletransformtest.cc +++ b/tests/gtest/avifsampletransformtest.cc @@ -30,7 +30,7 @@ class AvifExpression : public avifSampleTransformExpression { } void AddOperator(avifSampleTransformTokenType op) { avifSampleTransformToken& token = AddToken(); - token.type = static_cast(op); + token.type = op; } int32_t Apply() const { @@ -158,10 +158,10 @@ TEST_P(SampleTransformOperationTest, Apply) { INSTANTIATE_TEST_SUITE_P( Operations, SampleTransformOperationTest, - testing::Values(Op(AVIF_SAMPLE_TRANSFORM_NEGATE, 1, 0), - Op(AVIF_SAMPLE_TRANSFORM_NEGATE, -1, 1), - Op(AVIF_SAMPLE_TRANSFORM_NEGATE, 0, 0), - Op(AVIF_SAMPLE_TRANSFORM_NEGATE, -256, 255), + testing::Values(Op(AVIF_SAMPLE_TRANSFORM_NEGATION, 1, 0), + Op(AVIF_SAMPLE_TRANSFORM_NEGATION, -1, 1), + Op(AVIF_SAMPLE_TRANSFORM_NEGATION, 0, 0), + Op(AVIF_SAMPLE_TRANSFORM_NEGATION, -256, 255), Op(AVIF_SAMPLE_TRANSFORM_ABSOLUTE, 1, 1), Op(AVIF_SAMPLE_TRANSFORM_ABSOLUTE, -1, 1), Op(AVIF_SAMPLE_TRANSFORM_ABSOLUTE, 256, 255), @@ -176,8 +176,8 @@ INSTANTIATE_TEST_SUITE_P( Op(-1, AVIF_SAMPLE_TRANSFORM_DIFFERENCE, 1, 0), Op(1, AVIF_SAMPLE_TRANSFORM_PRODUCT, 1, 1), Op(2, AVIF_SAMPLE_TRANSFORM_PRODUCT, 3, 6), - Op(1, AVIF_SAMPLE_TRANSFORM_DIVIDE, 1, 1), - Op(2, AVIF_SAMPLE_TRANSFORM_DIVIDE, 3, 0), + Op(1, AVIF_SAMPLE_TRANSFORM_QUOTIENT, 1, 1), + Op(2, AVIF_SAMPLE_TRANSFORM_QUOTIENT, 3, 0), Op(1, AVIF_SAMPLE_TRANSFORM_AND, 1, 1), Op(1, AVIF_SAMPLE_TRANSFORM_AND, 2, 0), Op(7, AVIF_SAMPLE_TRANSFORM_AND, 15, 7), @@ -186,10 +186,10 @@ INSTANTIATE_TEST_SUITE_P( Op(1, AVIF_SAMPLE_TRANSFORM_XOR, 3, 2), Op(AVIF_SAMPLE_TRANSFORM_NOT, 254, 0), Op(AVIF_SAMPLE_TRANSFORM_NOT, -1, 0), - Op(AVIF_SAMPLE_TRANSFORM_MSB, 0, 0), - Op(AVIF_SAMPLE_TRANSFORM_MSB, -1, 0), - Op(AVIF_SAMPLE_TRANSFORM_MSB, 61, 5), - Op(AVIF_SAMPLE_TRANSFORM_MSB, + Op(AVIF_SAMPLE_TRANSFORM_BSR, 0, 0), + Op(AVIF_SAMPLE_TRANSFORM_BSR, -1, 0), + Op(AVIF_SAMPLE_TRANSFORM_BSR, 61, 5), + Op(AVIF_SAMPLE_TRANSFORM_BSR, std::numeric_limits::max(), 30), Op(2, AVIF_SAMPLE_TRANSFORM_POW, 4, 16), Op(4, AVIF_SAMPLE_TRANSFORM_POW, 2, 16),