SUTextureCreateFromFile() fails to set s_scale and t_scale

Since the I can’t raise issues on the GitHub C API tracker currently (non Trimble Employees can’t seem to do this), I’ll raise here.

I’ve been struggling to get textures working on my Ruby C Extension for some time. Specifically, I am trying to copy textures from one model to another. All seems to work except that the scale at which the textures are copied over is wrong (they are set to an s_scale and t_scale of 1.0).

I think I have narrowed down the problem to the C API function SUTextureCreateFromFile(). When this function is used to create a texture, the s_scale and t_scale is not saved. Here is some code that can replicate the issue - a Ruby C Extension function:

VALUE test_texture_scales(VALUE self)
{
  SUModelRef model = SU_INVALID;
  SU(SUApplicationGetActiveModel(&model));
  if (SUIsInvalid(model)) {
    rb_raise(rb_eTypeError, "invalid model");
  }
  // Test all materials with textures in model
  size_t num_materials = 0;
  SUModelGetNumMaterials(model, &num_materials);
  SUMaterialRef* materials = new SUMaterialRef[num_materials];
  size_t count = 0;
  SUModelGetMaterials(model, num_materials, materials, &count);
  for (size_t i = 0; i < count; ++i) {
    SUMaterialRef material = materials[i];
    SUTextureRef texture = SU_INVALID;
    SUResult res = SUMaterialGetTexture(material, &texture);
    if (res != SU_ERROR_NONE || SUIsInvalid(texture)) {
      continue;
    }
    size_t width = 0;
    size_t height = 0;
    double s_scale = 0.0;
    double t_scale = 0.0;
    res = SUTextureGetDimensions(texture, &width, &height, &s_scale, &t_scale);
    if (res != SU_ERROR_NONE) {
      std::cout << "Failed to get texture dimensions for material " << i << std::endl;
    }
    SUStringRef file_name = SU_INVALID;
    SUStringCreate(&file_name);
    SUTextureGetFileName(texture, &file_name);
    std::string name_str = GetString(file_name);
    std::cout << "In Model Texture: Name:"<< name_str <<" width:" << width << " height:" << height << " s_scale:" << s_scale << " t_scale:" << t_scale << std::endl;
// Prints: In Model Texture: Name:Roofing_Tile_Spanish.jpgx00 width:1392 height:1144 s_scale:0.0333333 t_scale:0.0405594
    SUStringRelease(&file_name);

    // Now copy texture to a new one
    std::string file_path = "/tmp/tmptexture";
    res = SUTextureWriteOriginalToFile(texture, file_path.c_str());
    assert(res == SU_ERROR_NONE);
    SUTextureRef new_texture = SU_INVALID;
    res = SUTextureCreateFromFile(&new_texture, file_path.c_str(), s_scale, t_scale);
    // Delete the temporary file
    const char *file_path_c = file_path.c_str();
    int ret = std::remove(file_path_c);
    if(ret != 0) {
      rb_raise(rb_eTypeError, "failed to delete temporary texture file");
    }
    // Check the scales are correct:
    size_t new_width = 0;
    size_t new_height = 0;
    double new_s_scale = 0.0;
    double new_t_scale = 0.0;
    res = SUTextureGetDimensions(new_texture, &new_width, &new_height, &new_s_scale, &new_t_scale);
    assert(res == SU_ERROR_NONE);
    std::cout << "Copied Texture: Name:"<< name_str <<" width:" << new_width << " height:" << new_height << " s_scale:" << new_s_scale << " t_scale:" << new_t_scale << std::endl;
// Prints: Copied Texture: Name:Roofing_Tile_Spanish.jpgx00 width:1392 height:1144 s_scale:1 t_scale:1
    SUTextureRelease(&new_texture);
  }

  delete [] materials;

  return self;
}

I think this is a bug in the API. I have tested this on SU 2024 and 2025. I suspect that someone has accidentally cast the s_scale and t_scale DOUBLES to a SIZE_T type or something. Am currently looking for a workaround.

As of SU 2025, one can use SUTextureSetDimensions() to manually set the s_scale and t_scale. Earlier versions only had SUTextureCreateFromFile as a way to set these, so another method needs to be found for versions 2024 and previous… [EDIT: I don’t think there is a workaround for versions prior to 2025 - this is broken!]

Have you tried using a TextureWriter ?

If this does not work, ImageRep class came out with SU2018.
You might try going through a ImageRep rather than a file. (Probably faster as well.)

Backstory: see past topic from 2018:

Setting Materials’ Texture scale - Developers / SketchUp SDK - SketchUp Community

… which resulted in me filing the API Issue:

C API function to set texture scale · Issue #40 · SketchUp/api-issue-tracker

… which was finally implemented seven years later with SU2025.

Ha! Yes, I saw that. Well done for spotting and filing all those years ago! I guess no one had tried to play with Textures much using the API since.

Thank you for those leads - I’ll try looking through those leads. However, ImageRep doesn’t seem to have a way of making a scaled Texture through SUTextureCreateFromImageRep. I don’t know enough about SUImageRepRef object itself yet, but I can’t at first glance see anything there that does texture scaling either. Have you worked with this class before?

If I have to use SU2025 and up, I can make it work for my project. At least anyone else bumping into this can see the issue.

1 Like

OK, so I have tested SUTextureSetDimensions in SU2025, and this is also broken.

In Model Texture: Name:Thom_Trousers_Diffuse.jpgx00 width:1000 height:1000 s_scale:0.163871 t_scale:0.163871
Copied Texture: Name:Thom_Trousers_Diffuse.jpgx00 width:1000 height:1000 s_scale:1 t_scale:1

…is the output from this code:

VALUE test_texture_image_rep(VALUE self)
{
  SUModelRef model = SU_INVALID;
  SU(SUApplicationGetActiveModel(&model));
  if (SUIsInvalid(model)) {
    rb_raise(rb_eTypeError, "invalid model");
  }
  // Test all materials with textures in model
  size_t num_materials = 0;
  SUModelGetNumMaterials(model, &num_materials);
  SUMaterialRef* materials = new SUMaterialRef[num_materials];
  size_t count = 0;
  SUModelGetMaterials(model, num_materials, materials, &count);
  for (size_t i = 0; i < count; ++i) {
    SUMaterialRef material = materials[i];
    SUTextureRef texture = SU_INVALID;
    SUResult res = SUMaterialGetTexture(material, &texture);
    if (res != SU_ERROR_NONE || SUIsInvalid(texture)) {
      continue;
    }
    size_t width = 0;
    size_t height = 0;
    double s_scale = 0.0;
    double t_scale = 0.0;
    res = SUTextureGetDimensions(texture, &width, &height, &s_scale, &t_scale);
    if (res != SU_ERROR_NONE) {
      std::cout << "Failed to get texture dimensions for material " << i << std::endl;
    }
    SUStringRef file_name = SU_INVALID;
    SUStringCreate(&file_name);
    SUTextureGetFileName(texture, &file_name);
    std::string name_str = GetString(file_name);
    std::cout << "In Model Texture: Name:"<< name_str <<" width:" << width << " height:" << height << " s_scale:" << s_scale << " t_scale:" << t_scale << std::endl;
    SUStringRelease(&file_name);

    // Now copy texture to a new one using ImageRep
    SUImageRepRef image_rep = SU_INVALID;
    SUImageRepCreate(&image_rep);
    res = SUTextureGetImageRep(texture, &image_rep);
    assert(res == SU_ERROR_NONE);
    SUTextureRef new_texture = SU_INVALID;
    res = SUTextureCreateFromImageRep(&new_texture, image_rep);
    assert(res == SU_ERROR_NONE);
    res = SUTextureSetDimensions(new_texture, s_scale, t_scale);
    // Check the scales are correct:
    size_t new_width = 0;
    size_t new_height = 0;
    double new_s_scale = 0.0;
    double new_t_scale = 0.0;
    res = SUTextureGetDimensions(new_texture, &new_width, &new_height, &new_s_scale, &new_t_scale);
    assert(res == SU_ERROR_NONE);
    std::cout << "Copied Texture: Name:"<< name_str <<" width:" << new_width << " height:" << new_height << " s_scale:" << new_s_scale << " t_scale:" << new_t_scale << std::endl;
    SUImageRepRelease(&image_rep);
    SUTextureRelease(&new_texture);
  }

  delete [] materials;

  return self;
}

I am on a Mac. This is a bit of a blow that there is no way to set the scales of textures.

Dan alerted a lot of people, and hopefully someone who knows C API better than I do can help.

I did check the code though, and everywhere that s_scale and t_scale are, they are set as double.

1 Like

Thanks, Colin,

I got it wrong - SUTextureSetDimensions is NOT broken in the way that I mentioned earlier (I was looking at the output of a different function). But it is still not correct. The actual outputs I get are:

In Model Texture: Name:Thom_Trousers_Diffuse.jpgx00 width:1000 height:1000 s_scale:0.163871 t_scale:0.163871 Copied Texture: Name:Thom_Trousers_Diffuse.jpgx00 width:1000 height:1000 s_scale:6.10236 t_scale:6.10236

… which is still wrong, BUT! s_scale of 0.163871 happens to be 1/6.10236. In other words, the scale being applied through SUTextureSetDimensions is not applied directly, but goes through some sort of (wrong) arithmetic conversion?

Do you remember this thread:

SUTextureGetDimensions scale issue - #6 by DanRathbun

?

1 Like

I did not! I should have looked. That is informative. It is confusing though.

The situation that currently exists is:

  1. s_scale and t_scale retrieved through SUTextureGetDimensions can be say 0.5 and 0.5
  2. using SUTextureSetDimensions, you set a texture to have s_scale and t_scale values of 0.5 and 0.5 (you would think it doesn’t change)
  3. calling SUTextureGetDimensions, from the texture that you set, would retrieve s_scale values of 2.0 and t_scale values of 2.0.

… not what one expects, but this is what happens!

In other words, SUTextureGetDimensions and SUTextureSetDimensions do not have their s_scale and t_scale values in the same format. They are to be precise, inverse to each other.

Instead of changing the code, which could break some people’s implementations, I would suggest that the names of the arguments passed to SUTextureSetDimensions be amended to si_scale and ti_scale respectively, and make a note in the docs that they are related, but not the same as SUTextureGetDimensions’s s_scale and t_scale - they are inverse

I looked at the unit test, and values of 1 and 4 are sent in, then checked to see if they are now 1 and 0.25. I couldn’t easily see why it’s the inverse.

Given what you now know, is there a problem, other than the variable name could be improved?

API documentation could definitely be improved.

On a related note, it is not clear how the same thing is done with the Ruby API. It has a method to set size in inches not scale factor.

This is a relatively new method. I wonder if many coders are even using it at all yet?

1 Like

I don’t know if this is practical, but I wonder if Trimble devs can search the extension warehouse for any symbols to SUTextureSetDimensions, to try to answer this question.

I think it would be better to fix SUTextureSetDimensions to behave as expected (i.e. have formats for s_values and t_values consistent with SUTextureGetDimensions ), IF the answer to the above question is “no”.

Otherwise, documenting this quirk would be ok too.

1 Like

Now that I have access to the Issue Tracker again, I have made the report of the issue here: SUTextureSetDimensions and SUTextureGetDimensions do not have s_scale and t_scale in the same format · Issue #1084 · SketchUp/api-issue-tracker · GitHub

1 Like