From d80b6cd56c00321a24d6d103d16cc08f3ce311dd Mon Sep 17 00:00:00 2001 From: Maksym Danylov Date: Thu, 21 May 2026 12:04:16 +0300 Subject: [PATCH] Prebid Mobile: makes creative size transformation based on pxRatio. --- .../server/auction/InterstitialProcessor.java | 30 ++++- .../ext/request/ExtRequestPrebidSdk.java | 17 ++- .../auction/InterstitialProcessorTest.java | 124 ++++++++++++++++++ 3 files changed, 168 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/prebid/server/auction/InterstitialProcessor.java b/src/main/java/org/prebid/server/auction/InterstitialProcessor.java index 7aa83960199..0384be9b8b9 100644 --- a/src/main/java/org/prebid/server/auction/InterstitialProcessor.java +++ b/src/main/java/org/prebid/server/auction/InterstitialProcessor.java @@ -10,7 +10,11 @@ import org.prebid.server.proto.openrtb.ext.request.ExtDevice; import org.prebid.server.proto.openrtb.ext.request.ExtDeviceInt; import org.prebid.server.proto.openrtb.ext.request.ExtDevicePrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidSdk; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -36,15 +40,17 @@ private BidRequest processBidRequest(BidRequest bidRequest) { if (extDeviceInt != null) { final int minWidthPerc = extDeviceInt.getMinWidthPerc(); final int minHeightPerc = extDeviceInt.getMinHeightPerc(); + final boolean usePxRatio = usePxRatio(bidRequest); final List updatedImps = bidRequest.getImp().stream() - .map(imp -> processInterstitialImp(imp, device, minWidthPerc, minHeightPerc)) + .map(imp -> processInterstitialImp(imp, device, minWidthPerc, minHeightPerc, usePxRatio)) .toList(); bidRequest = bidRequest.toBuilder().imp(updatedImps).build(); } return bidRequest; } - private Imp processInterstitialImp(Imp imp, Device device, int minWidthPerc, int minHeightPerc) { + private Imp processInterstitialImp(Imp imp, Device device, int minWidthPerc, int minHeightPerc, + boolean usePxRatio) { if (!isInterstitial(imp)) { return imp; } @@ -62,6 +68,11 @@ private Imp processInterstitialImp(Imp imp, Device device, int minWidthPerc, int if (maxHeight == null || maxWidth == null || (maxHeight == 1 && maxWidth == 1)) { maxHeight = device.getH(); maxWidth = device.getW(); + if (usePxRatio) { + final BigDecimal pxratio = device.getPxratio(); + maxHeight = deviceSizeToDips(maxHeight, pxratio); + maxWidth = deviceSizeToDips(maxWidth, pxratio); + } } if (maxHeight == null || maxWidth == null) { @@ -92,6 +103,21 @@ private ExtDeviceInt getExtDeviceInt(Device device) { return extDevicePrebid != null ? extDevicePrebid.getInterstitial() : null; } + private static boolean usePxRatio(BidRequest bidRequest) { + final ExtRequest extRequest = bidRequest.getExt(); + final ExtRequestPrebid prebid = extRequest != null ? extRequest.getPrebid() : null; + final ExtRequestPrebidSdk sdk = prebid != null ? prebid.getSdk() : null; + return sdk != null && Objects.equals(sdk.getUsePxRatio(), true); + } + + private static Integer deviceSizeToDips(Integer size, BigDecimal pxratio) { + if (size == null || pxratio == null || pxratio.signum() <= 0) { + return size; + } + + return Math.max(1, (int) Math.round(size / pxratio.doubleValue())); + } + private static class InterstitialSize { private static final List INTERSTITIAL_SIZES = new ArrayList<>(); diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebidSdk.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebidSdk.java index fe6ae9d55f8..396af74fdef 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebidSdk.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebidSdk.java @@ -1,5 +1,6 @@ package org.prebid.server.proto.openrtb.ext.request; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Value; import java.util.List; @@ -7,11 +8,25 @@ /** * Defines the contract for bidrequest.ext.prebid.sdk */ -@Value(staticConstructor = "of") +@Value public class ExtRequestPrebidSdk { /** * Defines the contract for bidrequest.ext.prebid.sdk.renderers */ List renderers; + + /** + * Defines the contract for bidrequest.ext.prebid.sdk.usepxratio + */ + @JsonProperty("usepxratio") + Boolean usePxRatio; + + public static ExtRequestPrebidSdk of(List renderers) { + return new ExtRequestPrebidSdk(renderers, null); + } + + public static ExtRequestPrebidSdk of(List renderers, Boolean usePxRatio) { + return new ExtRequestPrebidSdk(renderers, usePxRatio); + } } diff --git a/src/test/java/org/prebid/server/auction/InterstitialProcessorTest.java b/src/test/java/org/prebid/server/auction/InterstitialProcessorTest.java index 662c58e7aa8..a6d3908cfb1 100644 --- a/src/test/java/org/prebid/server/auction/InterstitialProcessorTest.java +++ b/src/test/java/org/prebid/server/auction/InterstitialProcessorTest.java @@ -11,6 +11,11 @@ import org.prebid.server.proto.openrtb.ext.request.ExtDevice; import org.prebid.server.proto.openrtb.ext.request.ExtDeviceInt; import org.prebid.server.proto.openrtb.ext.request.ExtDevicePrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidSdk; + +import java.math.BigDecimal; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -71,6 +76,28 @@ public void processShouldReturnBidRequestUpdatedWithInterstitialFormatsUsingSize Format.builder().w(320).h(481).build()); } + @Test + public void processShouldReturnBidRequestUpdatedWithInterstitialFormatsUsingDeviceSizeInDipsWhenFormatIsEmpty() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder().banner(Banner.builder().build()).instl(1).build())) + .device(Device.builder().w(1080).h(1920).pxratio(BigDecimal.valueOf(3)) + .ext(ExtDevice.of(null, ExtDevicePrebid.of(ExtDeviceInt.of(60, 60)))) + .build()) + .ext(usePxRatioExt()) + .build(); + + // when + final BidRequest result = interstitialProcessor.process(bidRequest); + + // then + assertThat(result.getImp()) + .extracting(Imp::getBanner) + .flatExtracting(Banner::getFormat) + .contains(Format.builder().w(320).h(480).build()) + .doesNotContain(Format.builder().w(768).h(1024).build()); + } + @Test public void processShouldReturnBidRequestUpdatedWithInterstitialFormatsUsingSizesFromDeviceWhenFormatIsOneToOne() { // given @@ -96,6 +123,97 @@ public void processShouldReturnBidRequestUpdatedWithInterstitialFormatsUsingSize Format.builder().w(320).h(481).build()); } + @Test + public void processShouldReturnBidRequestUpdatedWithInterstitialFormatsUsingDeviceSizeInDipsWhenFormatIsOneToOne() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder().banner(Banner.builder().format(singletonList( + Format.builder().w(1).h(1).build())).build()).instl(1).build())) + .device(Device.builder().w(1080).h(1920).pxratio(BigDecimal.valueOf(3)) + .ext(ExtDevice.of(null, ExtDevicePrebid.of(ExtDeviceInt.of(60, 60)))) + .build()) + .ext(usePxRatioExt()) + .build(); + + // when + final BidRequest result = interstitialProcessor.process(bidRequest); + + // then + assertThat(result.getImp()) + .extracting(Imp::getBanner) + .flatExtracting(Banner::getFormat) + .contains(Format.builder().w(320).h(480).build()) + .doesNotContain(Format.builder().w(768).h(1024).build()); + } + + @Test + public void processShouldNotConvertExplicitFormatSizeUsingDevicePxratio() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder().banner(Banner.builder() + .format(singletonList(Format.builder().w(400).h(600).build())).build()).instl(1) + .build())) + .device(Device.builder().w(1080).h(1920).pxratio(BigDecimal.valueOf(3)) + .ext(ExtDevice.of(null, ExtDevicePrebid.of(ExtDeviceInt.of(80, 80)))) + .build()) + .ext(usePxRatioExt()) + .build(); + + // when + final BidRequest result = interstitialProcessor.process(bidRequest); + + // then + assertThat(result.getImp()) + .extracting(Imp::getBanner) + .flatExtracting(Banner::getFormat) + .containsOnly(Format.builder().w(320).h(480).build(), + Format.builder().w(336).h(544).build(), + Format.builder().w(320).h(568).build(), + Format.builder().w(320).h(500).build(), + Format.builder().w(320).h(481).build()); + } + + @Test + public void processShouldKeepCurrentDeviceSizeBehaviorWhenUsePxRatioIsTrueAndDevicePxRatioIsAbsent() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder().banner(Banner.builder().build()).instl(1).build())) + .device(Device.builder().w(1080).h(1920) + .ext(ExtDevice.of(null, ExtDevicePrebid.of(ExtDeviceInt.of(1, 1)))) + .build()) + .ext(usePxRatioExt()) + .build(); + + // when + final BidRequest result = interstitialProcessor.process(bidRequest); + + // then + assertThat(result.getImp()) + .extracting(Imp::getBanner) + .flatExtracting(Banner::getFormat) + .contains(Format.builder().w(768).h(1024).build()); + } + + @Test + public void processShouldKeepCurrentDeviceSizeBehaviorWhenUsePxRatioIsAbsent() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder().banner(Banner.builder().build()).instl(1).build())) + .device(Device.builder().w(1080).h(1920).pxratio(BigDecimal.valueOf(3)) + .ext(ExtDevice.of(null, ExtDevicePrebid.of(ExtDeviceInt.of(1, 1)))) + .build()) + .build(); + + // when + final BidRequest result = interstitialProcessor.process(bidRequest); + + // then + assertThat(result.getImp()) + .extracting(Imp::getBanner) + .flatExtracting(Banner::getFormat) + .contains(Format.builder().w(768).h(1024).build()); + } + @Test public void processShouldReturnBidRequestUpdatedWithInterstitialFormatsLimitedByTen() { // given @@ -266,4 +384,10 @@ public void processShouldNotUpdateImpWhenInterstitialSizesWereNotFound() { .build()) .build()); } + + private static ExtRequest usePxRatioExt() { + return ExtRequest.of(ExtRequestPrebid.builder() + .sdk(ExtRequestPrebidSdk.of(null, true)) + .build()); + } }