Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add versioning for avatar image creation #1791

Merged
merged 3 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,87 +17,20 @@

package bisq.desktop.components.cathash;

import bisq.common.util.MathUtils;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.Optional;

@Slf4j
public class BucketConfig {
public abstract class BucketConfig {
static final String CURRENT_VERSION = "v1";
static final String DIGIT = "#";
static final String SHAPE_NUMBER = "#SHAPE_NUMBER#";

private static final Bucket BG = new Bucket(16, 0);
private static final Bucket BG_OVERLAY = new Bucket(32, 1);
private static final Bucket BODY_AND_FACE = new Bucket(16, 2);
private static final Bucket CHEST_AND_EARS = new Bucket(16, 3);
private static final Bucket CHEST_OVERLAY = new Bucket(3, 4);
private static final Bucket EARS_OVERLAY = new Bucket(3, 5);
private static final Bucket FACE_OVERLAY = new Bucket(17, 6);
private static final Bucket EYES = new Bucket(16, 7);
private static final Bucket NOSE = new Bucket(6, 8);
private static final Bucket WHISKERS = new Bucket(7, 9);
private static final Bucket BODY_SHAPE = new Bucket(2, 10);
private static final Bucket CHEST_SHAPE = new Bucket(2, 11);
private static final Bucket EARS_SHAPE = new Bucket(2, 12);
private static final Bucket FACE_SHAPE = new Bucket(5, 13);
abstract int[] getBucketSizes();

private static final int[] BUCKET_SIZES = new int[]{
BG.getCount(),
BG_OVERLAY.getCount(),
BODY_AND_FACE.getCount(),
CHEST_AND_EARS.getCount(),
CHEST_OVERLAY.getCount(),
EARS_OVERLAY.getCount(),
FACE_OVERLAY.getCount(),
EYES.getCount(),
NOSE.getCount(),
WHISKERS.getCount(),
BODY_SHAPE.getCount(),
CHEST_SHAPE.getCount(),
EARS_SHAPE.getCount(),
FACE_SHAPE.getCount()
};

private static final PathDetails[] PATH_TEMPLATES;

static {
String postFix = ".png";
PATH_TEMPLATES = new PathDetails[]{
new PathDetails("bg/bg_0/" + DIGIT + postFix, BG.getIdx()),
new PathDetails("bg/bg_1/" + DIGIT + postFix, BG_OVERLAY.getIdx()),
new PathDetails("body/body" + SHAPE_NUMBER + "/" + DIGIT + postFix, BODY_AND_FACE.getIdx(), BODY_SHAPE.getIdx()),
new PathDetails("chest/chest" + SHAPE_NUMBER + "_0/" + DIGIT + postFix, CHEST_AND_EARS.getIdx(), CHEST_SHAPE.getIdx()),
new PathDetails("chest/chest" + SHAPE_NUMBER + "_1/" + DIGIT + postFix, CHEST_OVERLAY.getIdx(), CHEST_SHAPE.getIdx()),
new PathDetails("ears/ears" + SHAPE_NUMBER + "_0/" + DIGIT + postFix, CHEST_AND_EARS.getIdx(), EARS_SHAPE.getIdx()),
new PathDetails("ears/ears" + SHAPE_NUMBER + "_1/" + DIGIT + postFix, EARS_OVERLAY.getIdx(), EARS_SHAPE.getIdx()),
new PathDetails("face/face" + SHAPE_NUMBER + "_0/" + DIGIT + postFix, BODY_AND_FACE.getIdx(), FACE_SHAPE.getIdx()),
new PathDetails("face/face" + SHAPE_NUMBER + "_1/" + DIGIT + postFix, FACE_OVERLAY.getIdx(), FACE_SHAPE.getIdx()),
new PathDetails("eyes/" + DIGIT + postFix, EYES.getIdx()),
new PathDetails("nose/" + DIGIT + postFix, NOSE.getIdx()),
new PathDetails("whiskers/" + DIGIT + postFix, WHISKERS.getIdx())
};

long numCombinations = getNumCombinations();
log.info("Number of combinations: 2^{} = {}", MathUtils.getLog2(numCombinations), numCombinations);
}

static int[] getBucketSizes() {
return BUCKET_SIZES;
}

static PathDetails[] getPathTemplates() {
return PATH_TEMPLATES;
}

static long getNumCombinations() {
long result = 1;
for (int bucketSize : BUCKET_SIZES) {
result *= bucketSize;
}
return result;
}
abstract PathDetails[] getPathTemplates();

@Getter
static class Bucket {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.desktop.components.cathash;

import bisq.common.util.MathUtils;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public final class BucketConfigV1 extends BucketConfig {
private static final Bucket BG = new Bucket(16, 0);
private static final Bucket BG_OVERLAY = new Bucket(32, 1);
private static final Bucket BODY_AND_FACE = new Bucket(16, 2);
private static final Bucket CHEST_AND_EARS = new Bucket(16, 3);
private static final Bucket CHEST_OVERLAY = new Bucket(3, 4);
private static final Bucket EARS_OVERLAY = new Bucket(3, 5);
private static final Bucket FACE_OVERLAY = new Bucket(17, 6);
private static final Bucket EYES = new Bucket(16, 7);
private static final Bucket NOSE = new Bucket(6, 8);
private static final Bucket WHISKERS = new Bucket(7, 9);
private static final Bucket BODY_SHAPE = new Bucket(2, 10);
private static final Bucket CHEST_SHAPE = new Bucket(2, 11);
private static final Bucket EARS_SHAPE = new Bucket(2, 12);
private static final Bucket FACE_SHAPE = new Bucket(5, 13);

private static final int[] BUCKET_SIZES = new int[]{
BG.getCount(),
BG_OVERLAY.getCount(),
BODY_AND_FACE.getCount(),
CHEST_AND_EARS.getCount(),
CHEST_OVERLAY.getCount(),
EARS_OVERLAY.getCount(),
FACE_OVERLAY.getCount(),
EYES.getCount(),
NOSE.getCount(),
WHISKERS.getCount(),
BODY_SHAPE.getCount(),
CHEST_SHAPE.getCount(),
EARS_SHAPE.getCount(),
FACE_SHAPE.getCount()
};

private static final PathDetails[] PATH_TEMPLATES;

static {
String postFix = ".png";
PATH_TEMPLATES = new PathDetails[]{
new PathDetails("bg/bg_0/" + DIGIT + postFix, BG.getIdx()),
new PathDetails("bg/bg_1/" + DIGIT + postFix, BG_OVERLAY.getIdx()),
new PathDetails("body/body" + SHAPE_NUMBER + "/" + DIGIT + postFix, BODY_AND_FACE.getIdx(), BODY_SHAPE.getIdx()),
new PathDetails("chest/chest" + SHAPE_NUMBER + "_0/" + DIGIT + postFix, CHEST_AND_EARS.getIdx(), CHEST_SHAPE.getIdx()),
new PathDetails("chest/chest" + SHAPE_NUMBER + "_1/" + DIGIT + postFix, CHEST_OVERLAY.getIdx(), CHEST_SHAPE.getIdx()),
new PathDetails("ears/ears" + SHAPE_NUMBER + "_0/" + DIGIT + postFix, CHEST_AND_EARS.getIdx(), EARS_SHAPE.getIdx()),
new PathDetails("ears/ears" + SHAPE_NUMBER + "_1/" + DIGIT + postFix, EARS_OVERLAY.getIdx(), EARS_SHAPE.getIdx()),
new PathDetails("face/face" + SHAPE_NUMBER + "_0/" + DIGIT + postFix, BODY_AND_FACE.getIdx(), FACE_SHAPE.getIdx()),
new PathDetails("face/face" + SHAPE_NUMBER + "_1/" + DIGIT + postFix, FACE_OVERLAY.getIdx(), FACE_SHAPE.getIdx()),
new PathDetails("eyes/" + DIGIT + postFix, EYES.getIdx()),
new PathDetails("nose/" + DIGIT + postFix, NOSE.getIdx()),
new PathDetails("whiskers/" + DIGIT + postFix, WHISKERS.getIdx())
};
}

@Override
int[] getBucketSizes() {
return BUCKET_SIZES;
}

@Override
PathDetails[] getPathTemplates() {
return PATH_TEMPLATES;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,49 @@ public class CatHash {
private static final ConcurrentHashMap<BigInteger, Image> CACHE = new ConcurrentHashMap<>();

public static Image getImage(UserProfile userProfile) {
return getImage(userProfile.getPubKeyHash(), userProfile.getProofOfWork().getSolution(), true);
return getImage(userProfile.getPubKeyHash(), userProfile.getProofOfWork().getSolution(),
userProfile.getAvatarVersion(), true);
}

public static Image getImage(byte[] pubKeyHash, byte[] powSolution) {
return getImage(pubKeyHash, powSolution, true);
public static Image getImage(byte[] pubKeyHash, byte[] powSolution, String avatarVersion) {
return getImage(pubKeyHash, powSolution, avatarVersion, true);
}

public static Image getImage(byte[] pubKeyHash, byte[] powSolution, boolean useCache) {
public static Image getImage(byte[] pubKeyHash, byte[] powSolution, String avatarVersion, boolean useCache) {
byte[] combined = ByteArrayUtils.concat(powSolution, pubKeyHash);
BigInteger input = new BigInteger(combined);
if (useCache && CACHE.containsKey(input)) {
return CACHE.get(input);
}

int[] buckets = BucketEncoder.encode(input, BucketConfig.getBucketSizes());
String[] paths = BucketEncoder.toPaths(buckets, BucketConfig.getPathTemplates());
BucketConfig bucketConfig = getBucketConfig(avatarVersion);
log.info("Getting user avatar image using class {}", bucketConfig.getClass().getName());

int[] buckets = BucketEncoder.encode(input, bucketConfig.getBucketSizes());
String[] paths = BucketEncoder.toPaths(buckets, bucketConfig.getPathTemplates());
Image image = ImageUtil.composeImage(paths, SIZE, SIZE);
if (useCache && CACHE.size() < MAX_CACHE_SIZE) {
CACHE.put(input, image);
}
return image;
}

public static String currentAvatarsVersion() {
return BucketConfig.CURRENT_VERSION;
}

private static BucketConfig getBucketConfig(String avatarVersion) {
BucketConfig bucketConfig;
switch (avatarVersion) {
case "v1": {
bucketConfig = new BucketConfigV1();
log.info("Creating v1 BucketConfig: {}", bucketConfig.getClass().getName());
}
default: {
bucketConfig = new BucketConfigV1();
log.info("Falling to default BucketConfig: {}", bucketConfig.getClass().getName());
}
}
return bucketConfig;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public UserProfileDisplay(@Nullable UserProfile userProfile) {

public UserProfileDisplay(@Nullable UserProfile userProfile, double size) {
super(10);

setAlignment(Pos.CENTER_LEFT);

userProfileIcon = new UserProfileIcon(size);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@

@Slf4j
public class CreateNewProfileStep2Controller implements InitWithDataController<CreateNewProfileStep2Controller.InitData> {
private static final String CURRENT_AVATARS_VERSION = CatHash.currentAvatarsVersion();

@Getter
@ToString
@EqualsAndHashCode
Expand Down Expand Up @@ -90,7 +92,7 @@ public void initWithData(InitData data) {
model.setProofOfWork(data.getProofOfWork());
model.getNickName().set(data.getNickName());
model.getNym().set(data.getNym());
model.getCatHashImage().set(CatHash.getImage(data.getPubKeyHash(), data.getProofOfWork().getSolution()));
model.getCatHashImage().set(CatHash.getImage(data.getPubKeyHash(), data.getProofOfWork().getSolution(), CURRENT_AVATARS_VERSION));
}

@Override
Expand Down Expand Up @@ -133,6 +135,7 @@ protected void onSave() {
model.getKeyPair(),
model.getPubKeyHash(),
model.getProofOfWork(),
CURRENT_AVATARS_VERSION,
model.getTerms().get(),
model.getStatement().get())
.whenComplete((chatUserIdentity, throwable) -> UIThread.run(() -> {
Expand All @@ -148,4 +151,4 @@ protected void onSave() {
protected void close() {
OverlayController.hide();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,7 @@ protected void onViewDetached() {
nym.textProperty().unbind();
terms.textProperty().unbindBidirectional(model.getTerms());
statement.textProperty().unbindBidirectional(model.getStatement());
saveButton.setOnAction(null);
cancelButton.setOnAction(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@

@Slf4j
public class CreateProfileController implements Controller {
private static final String CURRENT_AVATARS_VERSION = CatHash.currentAvatarsVersion();

protected final CreateProfileModel model;
@Getter
protected final CreateProfileView view;
Expand Down Expand Up @@ -119,6 +121,7 @@ protected void onCreateUserProfile() {
model.getKeyPair().orElseThrow(),
model.getPubKeyHash().orElseThrow(),
model.getProofOfWork().orElseThrow(),
CURRENT_AVATARS_VERSION,
"",
"")
.whenComplete((chatUserIdentity, throwable) -> UIThread.run(() -> {
Expand Down Expand Up @@ -155,7 +158,7 @@ private CompletableFuture<ProofOfWork> createProofOfWork(byte[] pubKeyHash) {
model.setProofOfWork(Optional.of(proofOfWork));
byte[] powSolution = proofOfWork.getSolution();
String nym = NymIdGenerator.generate(pubKeyHash, powSolution);
Image image = CatHash.getImage(pubKeyHash, powSolution);
Image image = CatHash.getImage(pubKeyHash, powSolution, CURRENT_AVATARS_VERSION);
model.getNym().set(nym);
model.getCatHashImage().set(image);
model.getPowProgress().set(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,12 @@ public CompletableFuture<UserIdentity> createAndPublishNewUserProfile(String nic
KeyPair keyPair,
byte[] pubKeyHash,
ProofOfWork proofOfWork,
String avatarVersion,
String terms,
String statement) {
String identityTag = nickName + "-" + Hex.encode(pubKeyHash);
return identityService.createNewActiveIdentity(identityTag, keyPair)
.thenApply(identity -> createUserIdentity(nickName, proofOfWork, terms, statement, identity))
.thenApply(identity -> createUserIdentity(nickName, proofOfWork, avatarVersion, terms, statement, identity))
.thenApply(userIdentity -> {
publishPublicUserProfile(userIdentity.getUserProfile(), userIdentity.getIdentity().getNetworkIdWithKeyPair().getKeyPair());
return userIdentity;
Expand Down Expand Up @@ -315,12 +316,14 @@ private CompletableFuture<BroadcastResult> publishPublicUserProfile(UserProfile

private UserIdentity createUserIdentity(String nickName,
ProofOfWork proofOfWork,
String avatarVersion,
String terms,
String statement,
Identity identity) {
checkArgument(nickName.equals(nickName.trim()) && !nickName.isEmpty(),
"Nickname must not have leading or trailing spaces and must not be empty.");
UserProfile userProfile = new UserProfile(nickName, proofOfWork, identity.getNetworkIdWithKeyPair().getNetworkId(), terms, statement);
UserProfile userProfile = new UserProfile(nickName, proofOfWork, avatarVersion,
identity.getNetworkIdWithKeyPair().getNetworkId(), terms, statement);
UserIdentity userIdentity = new UserIdentity(identity, userProfile);

synchronized (lock) {
Expand Down
Loading
Loading