// Copyright Epic Games, Inc. All Rights Reserved. #include "MuT/ASTOpImageCrop.h" #include "Containers/Map.h" #include "MuR/ModelPrivate.h" #include "MuR/RefCounted.h" #include "MuR/Types.h" #include "MuT/ASTOpConditional.h" #include "MuT/ASTOpSwitch.h" #include "MuT/ASTOpImagePatch.h" #include "MuT/ASTOpImagePixelFormat.h" #include "MuT/ASTOpImageLayer.h" #include "MuT/ASTOpImageLayerColor.h" #include "MuT/ASTOpImageRasterMesh.h" #include "MuT/ASTOpImageInterpolate.h" #include "MuT/ASTOpImagePlainColor.h" #include "MuT/ASTOpImageDisplace.h" namespace mu { ASTOpImageCrop::ASTOpImageCrop() : Source(this) { } ASTOpImageCrop::~ASTOpImageCrop() { // Explicit call needed to avoid recursive destruction ASTOp::RemoveChildren(); } bool ASTOpImageCrop::IsEqual(const ASTOp& InOther) const { if (InOther.GetOpType()==GetOpType()) { const ASTOpImageCrop* other = static_cast(&InOther); return Source == other->Source && Min == other->Min && Size == other->Size; } return false; } uint64 ASTOpImageCrop::Hash() const { uint64 res = std::hash()(GetOpType()); hash_combine(res, Source.child().get()); hash_combine(res, Min[0]); hash_combine(res, Min[1]); hash_combine(res, Size[0]); hash_combine(res, Size[1]); return res; } Ptr ASTOpImageCrop::Clone(MapChildFuncRef MapChild) const { Ptr n = new ASTOpImageCrop(); n->Source = MapChild(Source.child()); n->Min = Min; n->Size = Size; return n; } void ASTOpImageCrop::ForEachChild(const TFunctionRef Func) { Func(Source); } void ASTOpImageCrop::Link(FProgram& Program, FLinkerOptions*) { // Already linked? if (!linkedAddress) { OP::ImageCropArgs Args; FMemory::Memzero(Args); if (Source) { Args.source = Source->linkedAddress; } Args.minX = Min[0]; Args.minY = Min[1]; Args.sizeX = Size[0]; Args.sizeY = Size[1]; linkedAddress = (OP::ADDRESS)Program.OpAddress.Num(); Program.OpAddress.Add(Program.ByteCode.Num()); AppendCode(Program.ByteCode, GetOpType()); AppendCode(Program.ByteCode, Args); } } FImageDesc ASTOpImageCrop::GetImageDesc(bool bReturnBestOption, FGetImageDescContext* Context) const { FImageDesc Result; // Local context in case it is necessary FGetImageDescContext LocalContext; if (!Context) { Context = &LocalContext; } else { // Cached result? FImageDesc* PtrValue = Context->m_results.Find(this); if (PtrValue) { return *PtrValue; } } // Actual work if (Source) { Result = Source->GetImageDesc(bReturnBestOption, Context); //Result.m_lods = 1; Result.m_size[0] = Size[0]; Result.m_size[1] = Size[1]; } // Cache the result if (Context) { Context->m_results.Add(this, Result); } return Result; } Ptr ASTOpImageCrop::GetImageSizeExpression() const { Ptr Res = new ImageSizeExpression; Res->type = ImageSizeExpression::ISET_CONSTANT; Res->size[0] = Size[0]; Res->size[1] = Size[1]; return Res; } void ASTOpImageCrop::GetLayoutBlockSize(int32* OutBlockX, int32* OutBlockY) { // We didn't find any layout yet. *OutBlockX = 0; *OutBlockY = 0; // Try the source if (Source) { Source->GetLayoutBlockSize( OutBlockX, OutBlockY ); } } FSourceDataDescriptor ASTOpImageCrop::GetSourceDataDescriptor(FGetSourceDataDescriptorContext* Context) const { if (Source) { return Source->GetSourceDataDescriptor(Context); } return {}; } Ptr ASTOpImageCrop::OptimiseSemantic(const FModelOptimizationOptions& options, int32 Pass) const { Ptr Result; Ptr SourceAt = Source.child(); // The instruction can be sunk EOpType sourceType = SourceAt->GetOpType(); switch (sourceType) { case EOpType::IM_PLAINCOLOUR: { Ptr NewOp = mu::Clone(SourceAt.get()); NewOp->Size[0] = Size[0]; NewOp->Size[1] = Size[1]; NewOp->LODs = 1; // TODO Result = NewOp; break; } default: break; } return Result; } Ptr ASTOpImageCrop::OptimiseSink(const FModelOptimizationOptions&, FOptimizeSinkContext& context) const { Ptr Result; Ptr SourceAt = Source.child(); switch (SourceAt->GetOpType()) { // In case we have other operations with special optimisation rules. case EOpType::NONE: break; default: { Result = context.ImageCropSinker.Apply(this); break; } // default } return Result; } Ptr Sink_ImageCropAST::Apply(const ASTOpImageCrop* InRoot) { check(InRoot->GetOpType() == EOpType::IM_CROP); Root = InRoot; OldToNew.Reset(); InitialSource = InRoot->Source.child(); Ptr newSource = Visit(InitialSource, InRoot); // If there is any change, it is the new root. if (newSource != InitialSource) { return newSource; } return nullptr; } Ptr Sink_ImageCropAST::Visit(Ptr at, const ASTOpImageCrop* currentCropOp) { if (!at) return nullptr; // Already visited? const Ptr* Cached = OldToNew.Find({ at, currentCropOp }); if (Cached) { return *Cached; } bool skipSinking = false; Ptr newAt = at; switch (at->GetOpType()) { case EOpType::IM_CONDITIONAL: { // We move down the two paths Ptr newOp = mu::Clone(at); newOp->yes = Visit(newOp->yes.child(), currentCropOp); newOp->no = Visit(newOp->no.child(), currentCropOp); newAt = newOp; break; } case EOpType::IM_SWITCH: { // We move down all the paths Ptr newOp = mu::Clone(at); newOp->Default = Visit(newOp->Default.child(), currentCropOp); for (auto& c : newOp->Cases) { c.Branch = Visit(c.Branch.child(), currentCropOp); } newAt = newOp; break; } case EOpType::IM_PIXELFORMAT: { Ptr nop = mu::Clone(at); nop->Source = Visit(nop->Source.child(), currentCropOp); newAt = nop; break; } case EOpType::IM_PATCH: { const ASTOpImagePatch* typedPatch = static_cast(at.get()); Ptr rectOp = typedPatch->patch.child(); ASTOp::FGetImageDescContext context; FImageDesc patchDesc = rectOp->GetImageDesc(false, &context); box patchBox; patchBox.min[0] = typedPatch->location[0]; patchBox.min[1] = typedPatch->location[1]; patchBox.size[0] = patchDesc.m_size[0]; patchBox.size[1] = patchDesc.m_size[1]; box cropBox; cropBox.min[0] = currentCropOp->Min[0]; cropBox.min[1] = currentCropOp->Min[1]; cropBox.size[0] = currentCropOp->Size[0]; cropBox.size[1] = currentCropOp->Size[1]; if (!patchBox.IntersectsExclusive(cropBox)) { // We can ignore the patch newAt = Visit(typedPatch->base.child(), currentCropOp); } else { // Crop the base with the full crop, and the patch with the intersected part, // adapting the patch origin Ptr newOp = mu::Clone(at); newOp->base = Visit(newOp->base.child(), currentCropOp); box ibox = patchBox.Intersect2i(cropBox); check(ibox.size[0] > 0 && ibox.size[1] > 0); Ptr patchCropOp = mu::Clone(currentCropOp); patchCropOp->Min[0] = ibox.min[0] - patchBox.min[0]; patchCropOp->Min[1] = ibox.min[1] - patchBox.min[1]; patchCropOp->Size[0] = ibox.size[0]; patchCropOp->Size[1] = ibox.size[1]; newOp->patch = Visit(newOp->patch.child(), patchCropOp.get()); newOp->location[0] = ibox.min[0] - cropBox.min[0]; newOp->location[1] = ibox.min[1] - cropBox.min[1]; newAt = newOp; } break; } case EOpType::IM_CROP: { // We can combine the two crops into a possibly smaller crop const ASTOpImageCrop* childCrop = static_cast(at.get()); box childCropBox; childCropBox.min[0] = childCrop->Min[0]; childCropBox.min[1] = childCrop->Min[1]; childCropBox.size[0] = childCrop->Size[0]; childCropBox.size[1] = childCrop->Size[1]; box cropBox; cropBox.min[0] = currentCropOp->Min[0]; cropBox.min[1] = currentCropOp->Min[1]; cropBox.size[0] = currentCropOp->Size[0]; cropBox.size[1] = currentCropOp->Size[1]; // Compose the crops: in the final image the child crop is applied first and the // current ctop is applied to the result. So the final crop box would be: box ibox; ibox.min = childCropBox.min + cropBox.min; ibox.size[0] = FMath::Min(cropBox.size[0], childCropBox.size[0]); ibox.size[1] = FMath::Min(cropBox.size[1], childCropBox.size[1]); //check(cropBox.min.AllSmallerOrEqualThan(childCropBox.size)); //check((cropBox.min + cropBox.size).AllSmallerOrEqualThan(childCropBox.size)); //check((ibox.min + ibox.size).AllSmallerOrEqualThan(childCropBox.min + childCropBox.size)); // This happens more often that one would think if (ibox == childCropBox) { // the parent crop is not necessary skipSinking = true; } else if (ibox == cropBox) { // The child crop is not necessary Ptr childSource = childCrop->Source.child(); newAt = Visit(childSource, currentCropOp); } else { // combine into one crop Ptr newCropOp = mu::Clone(currentCropOp); newCropOp->Min[0] = ibox.min[0]; newCropOp->Min[1] = ibox.min[1]; newCropOp->Size[0] = ibox.size[0]; newCropOp->Size[1] = ibox.size[1]; Ptr childSource = childCrop->Source.child(); newAt = Visit(childSource, newCropOp.get()); } break; } case EOpType::IM_LAYER: { // We move the op down the arguments Ptr nop = mu::Clone(at); Ptr aOp = nop->base.child(); nop->base = Visit(aOp, currentCropOp); Ptr bOp = nop->blend.child(); nop->blend = Visit(bOp, currentCropOp); Ptr mOp = nop->mask.child(); nop->mask = Visit(mOp, currentCropOp); newAt = nop; break; } case EOpType::IM_LAYERCOLOUR: { // We move the op down the arguments Ptr nop = mu::Clone(at); Ptr aOp = nop->base.child(); nop->base = Visit(aOp, currentCropOp); Ptr mOp = nop->mask.child(); nop->mask = Visit(mOp, currentCropOp); newAt = nop; break; } case EOpType::IM_DISPLACE: { // We move the op down the arguments Ptr nop = mu::Clone(at); Ptr aOp = nop->Source.child(); nop->Source = Visit(aOp, currentCropOp); Ptr bOp = nop->DisplacementMap.child(); nop->DisplacementMap = Visit(bOp, currentCropOp); newAt = nop; break; } case EOpType::IM_RASTERMESH: { // We add cropping data to the raster mesh if it doesn't have any // \TODO: Is is possible to hit 2 crops on a raster mesh? Combine the crop. Ptr nop = mu::Clone(at); bool bRasterHasCrop = nop->UncroppedSizeX != 0; if (!bRasterHasCrop) { box cropBox; cropBox.min[0] = currentCropOp->Min[0]; cropBox.min[1] = currentCropOp->Min[1]; cropBox.size[0] = currentCropOp->Size[0]; cropBox.size[1] = currentCropOp->Size[1]; nop->UncroppedSizeX = nop->SizeX; nop->UncroppedSizeY = nop->SizeY; nop->CropMinX = cropBox.min[0]; nop->CropMinY = cropBox.min[1]; nop->SizeX = cropBox.size[0]; nop->SizeY = cropBox.size[1]; newAt = nop; } break; } case EOpType::IM_INTERPOLATE: { // Move the op down all the paths Ptr NewOp = mu::Clone(at); for (int32 v = 0; v < MUTABLE_OP_MAX_INTERPOLATE_COUNT; ++v) { Ptr child = NewOp->Targets[v].child(); Ptr bOp = Visit(child, currentCropOp); NewOp->Targets[v] = bOp; } newAt = NewOp; break; } default: break; } // end on line, replace with crop if (at == newAt && at != InitialSource && !skipSinking) { Ptr newOp = mu::Clone(currentCropOp); check(newOp->GetOpType() == EOpType::IM_CROP); newOp->Source = at; newAt = newOp; } OldToNew.Add({ at, currentCropOp }, newAt); return newAt; } }