• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

Check empty curves function broken photoshop 24.7.0

Contributor ,
Jul 28, 2023 Jul 28, 2023

Copy link to clipboard

Copied

I have this function as part of a script for finalising photoshop files. It checks for any points on the curve

 

this.checkCurve = function(index){
    
 //if(app.activeDocument.activeLayer.kind !=  LayerKind.CURVES ) return;
var ref = new ActionReference();
 ref.putIndex(s2t("layer"), index)
var rawDesc = executeActionGet( ref ).getList( s2t( 'adjustment' ) ).getObjectValue( 0 ).getData( s2t( 'legacyContentData' ) );
var pointer = 2;                                            // used to read rawData similar to reading a file
var flag = rawDesc.charCodeAt( pointer );       // char at offset 2 always == 1 so use to validate data
if( flag != 1 ) forceError;                             // unknown problem with rawData
   pointer = 6;// update pointer to BCD byte
   var bcd = rawDesc.charCodeAt( pointer );
   if( bcd < 0 || bcd > 15 ) forceError;// check for valid value
   if( bcd == 0 ) return false;// an empty adjustment - no curves to process
   return true;
 }

. The current version of photoshop doesnt seem to allow you to get the rawData. Anybody got some sort of work around or way of determining if a curve has no points on any of composite and component curves? 

TOPICS
Actions and scripting

Views

722

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Community Expert , Aug 21, 2023 Aug 21, 2023

Does this help? 

// check if curves layer has been changed at all;
// based on code by michael l hale;
// 2023, use it at your own risk;
if (app.documents.length > 0) {
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") ); 
var layerDesc = executeActionGet(ref);
try {
var aList = layerDesc.getList(stringIDToTypeID("adjustment"));
var listStuff = evaluateList (aList);
var listStuff2 = evaluateList (listStuff[0].getList(string
...

Votes

Translate

Translate
Adobe
Community Expert ,
Aug 21, 2023 Aug 21, 2023

Copy link to clipboard

Copied

Does this help? 

// check if curves layer has been changed at all;
// based on code by michael l hale;
// 2023, use it at your own risk;
if (app.documents.length > 0) {
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") ); 
var layerDesc = executeActionGet(ref);
try {
var aList = layerDesc.getList(stringIDToTypeID("adjustment"));
var listStuff = evaluateList (aList);
var listStuff2 = evaluateList (listStuff[0].getList(stringIDToTypeID("adjustment")))
var aaa = evaluateList(listStuff2[0].getList(stringIDToTypeID("curve")));
} catch (e) {
	alert ("either not a curves adjustment layer or at unedited default settings")
}
};
////////////////////////////////////
////// get list values //////
function evaluateList (aList) {
var listStuff = new Array;
for (var x = 0; x < aList.count; x++) {
var theType = aList.getType(x);
switch (theType) {
case DescValueType.ALIASTYPE:
listStuff.push( aList.getPath(x));
break;
case DescValueType.BOOLEANTYPE:
listStuff.push( aList.getBoolean(x));
break;
case DescValueType.CLASSTYPE:
listStuff.push( aList.getClass(x));
break;
case DescValueType.DOUBLETYPE:
listStuff.push( aList.getDouble(x));
break;
case DescValueType.ENUMERATEDTYPE:
listStuff.push( aList.getEnumerationValue(x));
break;
case DescValueType.INTEGERTYPE:
listStuff.push( aList.getInteger(x));
break;
case DescValueType.LISTTYPE:
listStuff.push( aList.getList(x));
break;
case DescValueType.OBJECTTYPE:
listStuff.push( (aList.getObjectValue(x)));
break;
case DescValueType.RAWTYPE:
listStuff.push( aList.getReference(x));
break;
case DescValueType.REFERENCETYPE:
listStuff.push( aList.getReference(x));
break;
case DescValueType.STRINGTYPE:
listStuff.push( aList.getString(x));
break;
case DescValueType.UNITDOUBLE:
listStuff.push( aList.getUnitDoubleValue(x));
break;
default: 
break;
};
};
return listStuff
};

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Aug 22, 2023 Aug 22, 2023

Copy link to clipboard

Copied

thank you ! but for me only the alert is shown 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 22, 2023 Aug 22, 2023

Copy link to clipboard

Copied

Please provide the file you are testing on. 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Sep 10, 2023 Sep 10, 2023

Copy link to clipboard

Copied

I tested once more. The code works for curve layer. but i need to get the values from a level layer.

something like this: 

var inBlackLevel = levelsOptions.inBlack;

var inWhiteLevel = levelsOptions.inWhite;

var outBlackLevel = levelsOptions.outBlack;

var outWhiteLevel = levelsOptions.outWhite;

var gammaLevel = levelsOptions.gamma;

alert(gammaLevel);

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Sep 10, 2023 Sep 10, 2023

Copy link to clipboard

Copied

in the var listStuff2 i get a alert [ActionDiscriptor]. But dont know how to get the level values.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 10, 2023 Sep 10, 2023

Copy link to clipboard

Copied

And again: Please provide the file you are testing on. 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Sep 10, 2023 Sep 10, 2023

Copy link to clipboard

Copied

//var aDoc = app.activeDocument;
//aDoc.activeLayer = aDoc.layers['Tonwertkorrektur 1'];





//https://community.adobe.com/t5/photoshop-ecosystem-discussions/modifying-levels-adjustment-layer/m-p/5587180

// working code before update
/*
var ref = new ActionReference();

//ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
//var data = executeActionGet(ref).getList(charIDToTypeID("Adjs")).getObjectValue(0).getData(stringIDToTypeID('legacyContentData'));

//var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID( 'Lyr ' ), charIDToTypeID( 'Ordn' ), charIDToTypeID( 'Trgt' ) );
var data = executeActionGet( ref ).getList( stringIDToTypeID( 'adjustment' ) ).getObjectValue( 0 ).getData( stringIDToTypeID( 'legacyContentData' ) );

//var data = get_prop_value("layer", null, "adjustment", 0, "legacyContentData");
var check = getShortFromStream( data, 0 ); // Prüfen ob Daten abgerufen werden können
if(check != 2 ) throwError;// if not 2 then something is wrong
var levelsOptions = {};
levelsOptions.inBlack = getShortFromStream( data, 2 );
levelsOptions.inWhite = getShortFromStream( data, 4 );
levelsOptions.outBlack = getShortFromStream( data, 6 );
levelsOptions.outWhite = getShortFromStream( data, 8 );
levelsOptions.gamma = getShortFromStream( data, 10 )/100;

//alert(levelsOptions.inBlack);

function getShortFromStream( stream, pointer ) {
    var hi, low;
    hi = stream.charCodeAt( pointer ) << 8 ;
    low = stream.charCodeAt( pointer + 1 );
    
    return hi + low;
 };

var inBlackLevel = levelsOptions.inBlack;
var inWhiteLevel = levelsOptions.inWhite;
var outBlackLevel = levelsOptions.outBlack;
var outWhiteLevel = levelsOptions.outWhite;
var gammaLevel = levelsOptions.gamma;
alert(gammaLevel);

*/

if (app.documents.length > 0) {
    var ref = new ActionReference();
    ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
    var layerDesc = executeActionGet(ref);
    try {
        var aList = layerDesc.getList(stringIDToTypeID("adjustment"));
        var listStuff = evaluateList (aList);
        var listStuff2 = evaluateList (listStuff[0].getList(stringIDToTypeID("adjustment")))
       // var aaa = evaluateList(listStuff2[0].getList(stringIDToTypeID("levels")));
        alert (listStuff2);
    } catch (e) {
        alert ("either not a curves adjustment layer or at unedited default settings")
    }
    
   
};
////////////////////////////////////
////// get list values //////
function evaluateList (aList) {
    var listStuff = new Array;
    for (var x = 0; x < aList.count; x++) {
        var theType = aList.getType(x);
        switch (theType) {
            case DescValueType.ALIASTYPE:
                listStuff.push( aList.getPath(x));
                break;
            case DescValueType.BOOLEANTYPE:
                listStuff.push( aList.getBoolean(x));
                break;
            case DescValueType.CLASSTYPE:
                listStuff.push( aList.getClass(x));
                break;
            case DescValueType.DOUBLETYPE:
                listStuff.push( aList.getDouble(x));
                break;
            case DescValueType.ENUMERATEDTYPE:
                listStuff.push( aList.getEnumerationValue(x));
                break;
            case DescValueType.INTEGERTYPE:
                listStuff.push( aList.getInteger(x));
                break;
            case DescValueType.LISTTYPE:
                listStuff.push( aList.getList(x));
                break;
            case DescValueType.OBJECTTYPE:
                listStuff.push( (aList.getObjectValue(x)));
                break;
            case DescValueType.RAWTYPE:
                listStuff.push( aList.getReference(x));
                break;
            case DescValueType.REFERENCETYPE:
                listStuff.push( aList.getReference(x));
                break;
            case DescValueType.STRINGTYPE:
                listStuff.push( aList.getString(x));
                break;
            case DescValueType.UNITDOUBLE:
                listStuff.push( aList.getUnitDoubleValue(x));
                break;
            default:
                break;
        };
    };
    return listStuff
};

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 10, 2023 Sep 10, 2023

Copy link to clipboard

Copied

I meant the image; never mind … 

Does this help?

 

updated code 

 

// check levels layer;
// based on code by michael l hale;
// 2023, use it at your own risk;
if (app.documents.length > 0) {
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") ); 
var layerDesc = executeActionGet(ref);
try {
var listStuff = evaluateList (layerDesc.getList(stringIDToTypeID("adjustment")));
var listStuff2 = evaluateList (listStuff[0].getList(stringIDToTypeID("adjustment")));
var theResult = new Array;
for (var m = 0; m < listStuff2.length; m++) {
var theChannel = listStuff2[m].getReference(stringIDToTypeID("channel"));
theChannel = typeIDToStringID(theChannel.getEnumeratedValue());
var theInput = evaluateList (listStuff2[m].getList(stringIDToTypeID("input")));
var theGamma = listStuff2[m].getDouble(stringIDToTypeID("gamma"));
var theOutput = evaluateList (listStuff2[m].getList(stringIDToTypeID("output")));
theResult.push(theChannel+"\nthe input\n"+theInput+"\nthe gamma\n"+theGamma+"\nthe output\n"+theOutput+"\n\n");
};
alert ("levels\n"+theResult.join("\n"))
} catch (e) {
	alert ("probably not a levels adjustment layer")
}
};
////////////////////////////////////
////// get list values //////
function evaluateList (aList) {
var listStuff = new Array;
for (var x = 0; x < aList.count; x++) {
var theType = aList.getType(x);
switch (theType) {
case DescValueType.ALIASTYPE:
listStuff.push( aList.getPath(x));
break;
case DescValueType.BOOLEANTYPE:
listStuff.push( aList.getBoolean(x));
break;
case DescValueType.CLASSTYPE:
listStuff.push( aList.getClass(x));
break;
case DescValueType.DOUBLETYPE:
listStuff.push( aList.getDouble(x));
break;
case DescValueType.ENUMERATEDTYPE:
listStuff.push( aList.getEnumerationValue(x));
break;
case DescValueType.INTEGERTYPE:
listStuff.push( aList.getInteger(x));
break;
case DescValueType.LISTTYPE:
listStuff.push( aList.getList(x));
break;
case DescValueType.OBJECTTYPE:
listStuff.push( (aList.getObjectValue(x)));
break;
case DescValueType.RAWTYPE:
listStuff.push( aList.getReference(x));
break;
case DescValueType.REFERENCETYPE:
listStuff.push( aList.getReference(x));
break;
case DescValueType.STRINGTYPE:
listStuff.push( aList.getString(x));
break;
case DescValueType.UNITDOUBLE:
listStuff.push( aList.getUnitDoubleValue(x));
break;
default: 
break;
};
};
return listStuff
};

 

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Sep 10, 2023 Sep 10, 2023

Copy link to clipboard

Copied

Nice work !!! thank you very much for the fast help!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 10, 2023 Sep 10, 2023

Copy link to clipboard

Copied

Mind you, that’s just for the composite channel. 

If you edited individal channels you need to amend the code further. 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Sep 10, 2023 Sep 10, 2023

Copy link to clipboard

Copied

works fine for me ! thanks 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 10, 2023 Sep 10, 2023

Copy link to clipboard

Copied

I updated the code to include the channels (if they have been edited, too). 

 

And feel free to mark a post as »Correct Answer« if it resoves the problem.

That’s not just about my vanity but also to enable potential future visitors to quickly find the relevant post/s. 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Sep 18, 2023 Sep 18, 2023

Copy link to clipboard

Copied

c.pfaffenbichler

Can you test how your code works if the user clicks the Auto button?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 18, 2023 Sep 18, 2023

Copy link to clipboard

Copied

Interesting, in that case the three auto-edited color Channels don’t appear to be listed individually and the try-clause errors out. 

Screenshot 2023-09-18 at 18.00.55.png

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 18, 2023 Sep 18, 2023

Copy link to clipboard

Copied

@r-bin , does this work for you? 

// check levels layer;
// based on code by michael l hale;
// 2023, use it at your own risk;
if (app.documents.length > 0) {
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") ); 
var layerDesc = executeActionGet(ref);
try {
var listStuff = evaluateList (layerDesc.getList(stringIDToTypeID("adjustment")));
var listStuff2 = evaluateList (listStuff[0].getList(stringIDToTypeID("adjustment")));
var theResult = new Array;
// check if auto;
if (listStuff2.length == 1 && listStuff2[0].hasKey(stringIDToTypeID("auto"))) {
var stuff = checkDesc2 (listStuff2[0]);
theResult.push(stuff)
}
else {
for (var m = 0; m < listStuff2.length; m++) {
var theChannel = listStuff2[m].getReference(stringIDToTypeID("channel"));
theChannel = typeIDToStringID(theChannel.getEnumeratedValue());
var theInput = evaluateList (listStuff2[m].getList(stringIDToTypeID("input")));
var theGamma = listStuff2[m].getDouble(stringIDToTypeID("gamma"));
var theOutput = evaluateList (listStuff2[m].getList(stringIDToTypeID("output")));
theResult.push(theChannel+"\nthe input\n"+theInput+"\nthe gamma\n"+theGamma+"\nthe output\n"+theOutput+"\n\n");
};
};
alert ("levels\n"+theResult.join("\n"))
} catch (e) {
	alert ("probably not a levels adjustment layer")
}
};
////////////////////////////////////
////// get list values //////
function evaluateList (aList) {
var listStuff = new Array;
for (var x = 0; x < aList.count; x++) {
var theType = aList.getType(x);
switch (theType) {
case DescValueType.ALIASTYPE:
listStuff.push( aList.getPath(x));
break;
case DescValueType.BOOLEANTYPE:
listStuff.push( aList.getBoolean(x));
break;
case DescValueType.CLASSTYPE:
listStuff.push( aList.getClass(x));
break;
case DescValueType.DOUBLETYPE:
listStuff.push( aList.getDouble(x));
break;
case DescValueType.ENUMERATEDTYPE:
listStuff.push( aList.getEnumerationValue(x));
break;
case DescValueType.INTEGERTYPE:
listStuff.push( aList.getInteger(x));
break;
case DescValueType.LISTTYPE:
listStuff.push( aList.getList(x));
break;
case DescValueType.OBJECTTYPE:
listStuff.push( (aList.getObjectValue(x)));
break;
case DescValueType.RAWTYPE:
listStuff.push( aList.getReference(x));
break;
case DescValueType.REFERENCETYPE:
listStuff.push( aList.getReference(x));
break;
case DescValueType.STRINGTYPE:
listStuff.push( aList.getString(x));
break;
case DescValueType.UNITDOUBLE:
listStuff.push( aList.getUnitDoubleValue(x));
break;
default: 
break;
};
};
return listStuff
};
////// based on code by michael l hale //////
function checkDesc2 (theDesc, theAlert) {
var c = theDesc.count;
var str = '';
for(var i=0;i<c;i++){ //enumerate descriptor's keys
str = str + 'Key '+i+' = '+typeIDToStringID(theDesc.getKey(i))+': '+theDesc.getType(theDesc.getKey(i))+'\n'+getValues (theDesc, i)+'\n';
};
if (theAlert == true) {alert("desc\n\n"+str);
return};
return str
};
////// check //////
function getValues (theDesc, theNumber) {
switch (theDesc.getType(theDesc.getKey(theNumber))) {
case DescValueType.ALIASTYPE:
return theDesc.getPath(theDesc.getKey(theNumber));
break;
case DescValueType.BOOLEANTYPE:
return theDesc.getBoolean(theDesc.getKey(theNumber));
break;
case DescValueType.CLASSTYPE:
return theDesc.getClass(theDesc.getKey(theNumber));
break;
case DescValueType.DOUBLETYPE:
return theDesc.getDouble(theDesc.getKey(theNumber));
break;
case DescValueType.ENUMERATEDTYPE:
return (typeIDToStringID(theDesc.getEnumerationValue(theDesc.getKey(theNumber)))+"_"+typeIDToStringID(theDesc.getEnumerationType(theDesc.getKey(theNumber))));
break;
case DescValueType.INTEGERTYPE:
return theDesc.getInteger(theDesc.getKey(theNumber));
break;
case DescValueType.LISTTYPE:
return theDesc.getList(theDesc.getKey(theNumber));
break;
case DescValueType.OBJECTTYPE:
return (theDesc.getObjectValue(theDesc.getKey(theNumber))+"_"+typeIDToStringID(theDesc.getObjectType(theDesc.getKey(theNumber))));
break;
case DescValueType.RAWTYPE:
return theDesc.getReference(theDesc.getData(theNumber));
break;
case DescValueType.REFERENCETYPE:
return theDesc.getReference(theDesc.getKey(theNumber));
break;
case DescValueType.STRINGTYPE:
return theDesc.getString(theDesc.getKey(theNumber));
break;
case DescValueType.UNITDOUBLE:
return (theDesc.getUnitDoubleValue(theDesc.getKey(theNumber))+"_"+typeIDToStringID(theDesc.getUnitDoubleType(theDesc.getKey(theNumber))));
break;
default: 
break;
};
};

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Sep 19, 2023 Sep 19, 2023

Copy link to clipboard

Copied

quote

@r-bin , does this work for you? 


By @c.pfaffenbichler

 

I can't check anything since I don't have Photoshop 2023. In any case, there is no way to get values for adjustment layers when using auto mode. The problem was identified in this thread https://community.adobe.com/t5/photoshop-ecosystem-discussions/photoshop-script-to-invert-curves/m-p...

I don't have a solution due to the lack of ability to work with the latest versions of Photoshop. You can try to solve it by applying my attempts in that topic.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 19, 2023 Sep 19, 2023

Copy link to clipboard

Copied

The last Script I posted would essentially just give the info that »auto« is in effect – see screenshot.  

Thanks for pointing out the »auto«-issue, I am not sure if I manage to look into it – much less find a solution. 

Screenshot 2023-09-19 at 09.58.27.png

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 23, 2023 Sep 23, 2023

Copy link to clipboard

Copied

@r-bin , I think the easiest route may be a (crude) work-around:

Editing the Composite Channel gets rid of the »auto«-designation, so one can get the settings of the individual color Channels and then undo the change to the Composite Channel itself. 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
People's Champ ,
Sep 23, 2023 Sep 23, 2023

Copy link to clipboard

Copied

LATEST

Well, how can you find out the parameters of the composite channel in this case? The rollback method is not very good. I'm not sure that it will work in "SuspendHistory" mode. In that thread, I attempted to falsely change a layer with empty parameters. They all worked for me in 2020. However, a person with a new Photoshop (like beta) claimed that he always had a “program error”. I checked the absence of "auto" using the json parameters for the layer. There is no trace of 'auto' in the adjustment layer itself in the "adjustment" list. It is not clear why they removed LegacyRawData. Can you try the methods that I did purely theoretically in that topic?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Sep 18, 2023 Sep 18, 2023

Copy link to clipboard

Copied

Thank you so much for this, Apologies for the delay in replying i've been away. It has absolutely done the trick! Many thanks 

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 18, 2023 Sep 18, 2023

Copy link to clipboard

Copied

You’re welcome! 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines